getSubscriptionModel()->getSubscriptionByAppId($appId); if (!$subscription || empty($subscription['refresh_token'])) { $msg = '[ WeChat ] [ Subscription ] [ API ] error: AppId:' . $appId . ' refresh_token or subscription info get fail'; return $this->setCode(ErrorCodeConstants::DB_ERROR_SELECT)->setMsg($msg)->getReturn(); } $refresh_token = $subscription['refresh_token']; LogService::debug("Subscription Get OfficialAccount Appid:{$appId} RefreshToken:{$refresh_token}"); $officialAccount = OpenPlatformService::instance()->getOfficialAccount($appId, $refresh_token); $officialAccount->server->push(function ($message) use ($officialAccount, $subscription) { $time = Request::instance()->server('REQUEST_TIME'); LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . json_encode(var_export($message, true), JSON_UNESCAPED_UNICODE)); //订阅号交互管理 if (isset($message['Event']) && $message['Event'] == 'unsubscribe') { //取消关注 OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->unsubscribe($time); } else { //添加用户Fans OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->createFans($officialAccount, $time); } //更新粉丝的交互时间 OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->updateOperateTime(); //消息回复 switch ($message['MsgType']) { case 'text': if ($message['Content'] == '$openid$') { return $message['FromUserName']; } if ($message['Content'] == '$userid$') { $userId = OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->getFansId(); return new Text($userId); } break; } return false; }); $officialAccount->server->serve()->send(); return $this->getReturn(); } catch (\Exception $e) { return $this->getExceptionReturn($e); } } /** * 全网发布事件 * @param $appId * @return \app\main\model\object\ReturnObject */ public function eventForAllNet($appId) { try { $server = OpenPlatformService::instance()->getOfficialAccount($appId)->server; $server->push(function ($message) { $messageObject = (new EventObject())->bind($message); LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . $messageObject->toJson()); switch ($messageObject->MsgType) { case MessageConstants::MESSAGE_TYPE_EVENT: if (in_array($messageObject->ToUserName, OpenPlatformConstants::$event_callback_user)) { return $messageObject->Event . 'from_callback'; } break; case MessageConstants::MESSAGE_TYPE_TEXT: // 文本 if ($messageObject->Content == 'TESTCOMPONENT_MSG_TYPE_TEXT') { return 'TESTCOMPONENT_MSG_TYPE_TEXT_callback'; } if (strpos($messageObject->Content, 'QUERY_AUTH_CODE:') === 0) { $query_auth_code = str_replace('QUERY_AUTH_CODE:', '', $messageObject->Content); LogService::info('[ WeChat ] [ MP ] [ API ] QUERY_AUTH_CODE: ' . json_encode(var_export($query_auth_code, true), JSON_UNESCAPED_UNICODE)); $auth = OpenPlatformService::instance()->openPlatform->handleAuthorize($query_auth_code); // 使用授权码换取接口调用凭据和授权信息 LogService::info('[ WeChat ] [ MP ] [ API ] AUTH: ' . json_encode(var_export($auth, true), JSON_UNESCAPED_UNICODE)); $app = OpenPlatformService::instance()->getOpenPlatform()->officialAccount($auth['authorization_info']['authorizer_appid'], $auth['authorization_info']['authorizer_refresh_token']); $app->customer_service ->message(new Text($query_auth_code . '_from_api')) ->to($messageObject->FromUserName) ->send(); } break; default: break; } return true; }); $server->serve()->send(); return $this->getReturn(); } catch (\Exception $e) { return $this->getExceptionReturn($e); } } /** * 检查平台id有效性 * @param $platform_id * @return ReturnObject */ public function checkOphostLegal($platform_id) { $sourceDomain = UrlService::instance()->getSourceDomain(); //检测是否是用户默认平台 $ophost = OfficialAccountsService::instance()->getOphostModel()->getInfoByHost($sourceDomain); if ($ophost) { //非法用户判断 if (isset($ophost['platform_id']) && $platform_id != $ophost['platform_id']) { return $this->setData(false)->getReturn(); } } else { return $this->setData(false)->getReturn(); } return $this->setData(true)->getReturn(); } /** * @param $appId * @return \app\main\model\object\ReturnObject */ public function eventForNormal($appId) { try { $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId); if (!$adminConfig) { $this->setLimitCache($appId); return $this->setMsg('渠道信息无效!' . $appId)->getReturn(); } $adminConfigObject = (new AdminConfigObject())->bind($adminConfig); //检测平台有效性 if (!$this->checkOphostLegal($adminConfigObject->platform_id)->data) { return $this->setMsg('平台无效!' . $adminConfigObject->platform_id)->getReturn(); } $refreshToken = OpenPlatformService::instance()->getRefreshToken($adminConfigObject->admin_id); $server = OpenPlatformService::instance()->getOfficialAccount($appId, $refreshToken)->server; $server->push(function ($message) use ($adminConfigObject) { $messageObject = (new EventObject())->bind($message); $this->wechatMsg = $messageObject; return $this->eventCallBack($messageObject, $adminConfigObject)->data; }); $server->serve()->send(); if ($this->wechatMsg->MsgType == MessageConstants::MESSAGE_TYPE_EVENT) { Log::info("EVENT_ANALYSIS:" . $this->wechatMsg->Event . "-" . $adminConfigObject->admin_id . ",time:" . (microtime(true) - THINK_START_TIME)); } return $this->getReturn(); } catch (\Exception $e) { return $this->getExceptionReturn($e); } } /** * 用户注册回调 */ public function processCallbackUserInfo($appId) { $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId); if ($adminConfig) { $list = Redis::instance()->hGet("callback_user", $adminConfig['admin_id']); Redis::instance()->hDel('callback_user', $adminConfig['admin_id']); if ($list) { $list = json_decode($list, true); foreach ($list as $message) { $channel_id = $adminConfig['admin_id']; $messageObj = (new EventObject())->bind($message); $userId = UserService::instance()->weChatInsertUser($channel_id, $messageObj->FromUserName)->data; if ($userId) { $cacheKey = CacheConstants::getQrcodeVisitCache($messageObj->FromUserName); if ($id = Redis::instance()->get($cacheKey)) { $oAna = new AnalysisObject(); $oAna->type = KafkaDotConstants::TYPE_VISIT; $oAna->event_time = Request::instance()->server('REQUEST_TIME'); $oAna->user_from = [ 'qrcode_id' => $id ]; KafkaDotService::instance()->sendMsg($userId, $oAna); } switch ($messageObj->Event) { case MessageConstants::MESSAGE_EVENT_SUBSCRIBE: $this->callbackSubscribe($channel_id, $messageObj); break; case MessageConstants::MESSAGE_EVENT_CLICK: if ($messageObj->EventKey == '签到') { $this->sendShareText($appId, $channel_id, $messageObj->FromUserName); } else{ $this->sendText($appId, $channel_id, $messageObj->FromUserName); } break; } //更新用户交互时间 UserService::instance()->updateUserOperateTime($userId, $messageObj->Event); } } } } } public function sendText($appId, $channel_id, $openid) { //如果Channel_id 是二维码图片菜单,则,查询回复的内容文字 $adminInfo = AdminService::instance()->getAdminConfigModel()->getAdminInfoAll($channel_id); if($adminInfo['is_qrcode_menu'] == '1'){ $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId); $wechat = new WeChatObject($adminConfig); $officialAccount = $wechat->getOfficialAccount(); $event_key = $this->wechatMsg->EventKey; $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([ 'admin_id' => $channel_id, 'eventkey' => $event_key, 'status' => 'normal', ])->find(); // 点击菜单'联系客服'时,event_key='manager_contact',此时$result=null if ($result) { $text = '请点击图片,长按识别二维码,进入[' . $result->title . ']'; //调用客服消息 $news = new Text($text); $officialAccount->customer_service ->message($news) ->to($openid) ->send(); } } } /** * 关注事件的异步处理 * @param $channel_id * @param EventObject $eventObject * @return bool */ public function callbackSubscribe($channel_id, EventObject $eventObject) { $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channel_id, $eventObject->FromUserName); $userKey = CacheConstants::getUserCacheKey($userId); $redis = Redis::instance(); $request_time = Request::instance()->server('REQUEST_TIME'); try { /* * php打点,关注 */ $wechat = OpenPlatformService::instance()->officialAccount; $wechat->http_client = new Client(Config::get('wechat.http')); $wechatUser = $wechat->user->get($eventObject->FromUserName); //获取微信用户 if (array_key_exists('errcode', $wechatUser)) { LogService::notice("获取微信用户失败:" . json_encode($wechatUser)); return false; } $updateData = new UserObject(); $updateData->is_subscribe = UserConstants::USER_IS_SUBSCRIBE_YES; $updateData->avatar = $wechatUser['headimgurl']; $updateData->nickname = $wechatUser['nickname']; $updateData->sex = $wechatUser['sex']; $updateData->subscribe_time = $request_time; if ($userId) { //判断是否有此用户 $oldUser = UserService::instance()->getUserModel()->getUserInfo($userId); //用户关注推广链接为空时,更新用户关注推广链接 $user_follow_referral_id = $oldUser['follow_referral_id'] ?? null; $referral_id = $oldUser['referral_id']; if(!$user_follow_referral_id && $referral_id){ $updateData->follow_referral_id = $referral_id; $user_follow_referral_id = $referral_id; } $oldFollowTime = $oldUser['subscribe_time']; $res = UserService::instance()->update($updateData->toArray(), ['id' => $userId]); //更新关注状态 关注时间 昵称 头像等 if ($redis->exists($userKey)) { $redis->hmset($userKey, $updateData->toArray()); $redis->expire($userKey, 86400); } $dotData = new DotObject(); //打点信息初始化 $dotData->channel_id = $channel_id; $dotData->user_id = $userId; $dotData->event_time = $request_time;; $dotData->is_first_follow = UserConstants::USER_IS_FIRST_FOLLOW_YES; //新增关注用户统计 if ($oldUser && $res) { if ($oldUser['is_first_unfollow'] != UserConstants::USER_IS_FIRST_UNFOLLOW_NEVER) { $dotData->is_first_follow = UserConstants::USER_IS_FIRST_FOLLOW_NO; } //新增性别打点 if ($oldUser['sex'] == UserConstants::USER_SEX_UNKNOWN && $updateData->sex) { //性别变更多打一次点 $oSexAna = new AnalysisObject(); $oSexAna->event_time = $request_time; $oSexAna->data['sex'] = $updateData->sex; $oSexAna->data['user_collect'] = 1; $oSexAna->type = KafkaDotConstants::TYPE_SEX; KafkaDotService::instance()->sendMsg($userId, $oSexAna); $newDotData = $dotData; $newDotData->sex = $updateData->sex; $newDotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE; $newDotData->type = MqConstants::MSG_TYPE_SEX; MqService::instance()->sendMessage($dotData); } } $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE; $dotData->type = MqConstants::MSG_TYPE_FOLLOW; MqService::instance()->sendMessage($dotData); $oAna = new AnalysisObject(); $oAna->event_time = $request_time; $oAna->type = [KafkaDotConstants::TYPE_SUBSCRIBE]; //php打点推广链接关注,以第一次关注推广链接为准 if ($user_follow_referral_id) { $oAna->user_from = [ 'referral_id' => $user_follow_referral_id ]; $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE_REFERER; $dotData->referral_id = $user_follow_referral_id; MqService::instance()->sendMessage($dotData); } $oAna->data['user_collect'] = 1; KafkaDotService::instance()->sendMsg($userId, $oAna); if ($oldUser['is_first_unfollow'] == 2 && Config::get('site.is_open_share') == "1"){//判断是不是分享链接的用户 && 分享活动开关配置为开 ShareService::instance()->attentionUser($userId,$wechatUser); } if ($oldFollowTime == 0 && $referral_id) { //首次关注回源 $url = getCurrentDomain($channel_id,"/t/{$referral_id}"); ReferralService::instance()->sendReferralAdData($referral_id, ['admin_id' => $channel_id, 'url' => $url, 'sub' => ['productName' => "关注"], 'openid' => $oldUser['openid']]); } } } catch (\Exception $exception) { LogService::exception($exception); if ($userId) { $upUserData = ['is_subscribe' => UserConstants::USER_IS_SUBSCRIBE_YES, 'subscribe_time' => $request_time]; UserService::instance()->update($upUserData, ['id' => $userId]); if ($redis->exists($userKey)) { $redis->hmset($userKey, $upUserData); $redis->expire($userKey, 86400); } } } } /** * 更新模板回调缓存 * @param $appid */ public function updateTplCallback($appid) { $cacheKey = "TPL_CALLBACK_" . $appid; Redis::instance()->incr($cacheKey); Redis::instance()->expire($cacheKey, Config::get("app_callback_tpl_cache")); if (Redis::instance()->get($cacheKey) >= Config::get("app_callback_tpl_threshold")) { $this->setLimitCache($appid); } } /** * 添加限制缓存 * @param $appid */ public function setLimitCache($appid) { LogService::info('appid limited:' . $appid); file_put_contents(sys_get_temp_dir() . '/TPL_CALLBACK_' . $appid, time()); } /** * @param $messageObject EventObject * @param $adminConfigObject AdminConfigObject * @return \app\main\model\object\ReturnObject */ public function eventCallBack($messageObject, $adminConfigObject) { try { LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . $messageObject->toJson()); if ($messageObject->Event == 'MASSSENDJOBFINISH') { // 高级群发JOB推送 SendChannelMsgService::instance()->saveSuccessCount($messageObject->MsgID, $messageObject->SentCount, $adminConfigObject->admin_id); } if ($messageObject->Event == 'TEMPLATESENDJOBFINISH') { $this->updateTplCallback($adminConfigObject->appid); return $this->setData('')->getReturn(); } if (!$messageObject->FromUserName) { LogService::error("用户信息为空"); return $this->setData('')->getReturn(); } //将非取消关注的事件存入缓存 if ($messageObject->Event != MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE) { $data = $messageObject->toArray(); $cache = Redis::instance()->hGet('callback_user', $adminConfigObject->admin_id); if ($cache) { $cache = json_decode($cache, true); $cache[] = $data; }else{ $cache = [$data]; } Redis::instance()->hSet("callback_user", $adminConfigObject->admin_id, json_encode($cache)); } $replayData = false; switch ($messageObject->MsgType) { case MessageConstants::MESSAGE_TYPE_EVENT: switch ($messageObject->Event) { case MessageConstants::MESSAGE_EVENT_SCAN: //永久二维码已关注 $replayData = $this->weChatPermanentQRCodeFollow($adminConfigObject, $messageObject, true); if (!$replayData) { $replayData = $this->getSubscribe($adminConfigObject->admin_id, $messageObject->FromUserName); } break; case MessageConstants::MESSAGE_EVENT_SUBSCRIBE: // 关注 $replayData = $this->eventSubscribe($adminConfigObject, $messageObject); break; case MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE: // 取消关注 //取消订阅不需要回复 $this->eventUnsubscribe($adminConfigObject->admin_id, $messageObject->FromUserName); break; case MessageConstants::MESSAGE_EVENT_CLICK: //点击 $replayData = $this->autoReply($messageObject->ToUserName, $messageObject->FromUserName, $messageObject->EventKey, MessageConstants::MESSAGE_TYPE_EVENT_YES); if ($replayData instanceof ReplyMsgObject && $replayData->msg_mark != MarkConstants::MARK_CLICK_INNER_KEYWORDS) { $replayData->msg_mark = MarkConstants::MARK_CLICK_PRE_RESOURCE; } break; } break; case MessageConstants::MESSAGE_TYPE_TEXT: // 文本 $replayData = $this->eventText($messageObject, $adminConfigObject); break; } $replayData = $this->setMarkForReplyData($replayData, $messageObject)->data; return $this->setData($replayData)->getReturn(); } catch (\Exception $e) { LogService::exception($e); return $this->setData('')->getReturn(); } } /** * 添加打点内容 * @param $data * @param EventObject $eventObject * @return ReturnObject|News|Text */ public function setMarkForReplyData($data, EventObject $eventObject) { $appid = Request::instance()->param('appid'); if (!($data instanceof ReplyMsgObject)) { return $this->setData($data)->getReturn(); }else{ LogService::debug("RESOURCE:".$data->toJson()); } $push_id = ''; if ($data->msg_mark == MarkConstants::MARK_QR_RESOURCE) { $push_id = (string)$eventObject->EventKey; } if ($data->push_id) { $push_id = (string)$data->push_id; } $push_time = time(); $mark = $data->msg_mark; if ($data->msg_type == MarkConstants::MSG_TYPE_TEXT) { preg_match_all("", $data->msg_content, $match); if ($match) { $replace = []; foreach ($match[1] as $index => $item) { $push_idx = $index + 1; $parse = parse_url($item); if (array_key_exists('host', $parse)) { if (preg_match('/^' . $appid . '/', $parse['host'])) { if (array_key_exists('query', $parse)) { $query = parse_query($parse['query']); if (array_key_exists('book_id', $query) && !$push_id) { $push_id = (string)$query['book_id']; } $item .= '&' . $this->processExt($mark, $push_id, $push_idx, $push_time); } else { $item .= '?' . $this->processExt($mark, $push_id, $push_idx, $push_time); } } } $replace[] = $item; } $data->msg_content = StringHelper::replaceOneByOne($data->msg_content, $match[1], $replace); //替换占位符 if (strrpos($data->msg_content, '{ZHIDING}')) { $top = model('ManageKey')->getOneByKey('top'); $data->msg_content = str_replace('{ZHIDING}', cdnurl($top['image']), $data->msg_content); } if (strrpos($data->msg_content, '{KANDIAN}')) { $kandian = Config::get('site.kandian_sign'); $data->msg_content = str_replace('{KANDIAN}', $kandian, $data->msg_content); } } LogService::debug($data->msg_content); return $this->setData(new Text($data->msg_content))->getReturn(); } if ($data->msg_type == MarkConstants::MSG_TYPE_NEWS) { $news = []; foreach ($data->msg_content as $index => $item) { $push_idx = $index + 1; $parse = parse_url($item['url']); if (array_key_exists('query', $parse)) { $query = parse_query($parse['query']); if (array_key_exists('book_id', $query) && !$push_id) { $push_id = (string)$query['book_id']; } $item['url'] .= '&' . $this->processExt($mark, $push_id, $push_idx, $push_time); } else { $item['url'] .= '?' . $this->processExt($mark, $push_id, $push_idx, $push_time); } $news[] = new NewsItem($item); } return $this->setData(new News($news))->getReturn(); } return $this->setData($data)->getReturn(); } public function processExt($mark, $push_id, $push_idx, $push_time) { $data = json_encode([ 'mark' => $mark, 'push_id' => $push_id, 'push_idx' => $push_idx, 'push_time' => $push_time, ]); return 'ext=' . urlencode($data); } /** * @param EventObject $message * @param AdminConfigObject $adminConfig * @return bool|News|Text|string * @throws \Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function eventText(EventObject $message, AdminConfigObject $adminConfig) { $object = new ReplyMsgObject(); /** * 自动回复处理 */ $autoReply = $this->autoReply($message->ToUserName, $message->FromUserName, $message->Content); if ($autoReply) { if ($autoReply instanceof ReplyMsgObject && $autoReply->msg_mark == MarkConstants::MARK_RESOURCE){ $autoReply->msg_mark = MarkConstants::MARK_REPLY_PRE_RESOURCE; } return $autoReply; } if ($adminConfig->search_tip) { $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig->admin_id, $message->FromUserName); $channel_id = AdminService::instance()->getAdminExtendModel()->getChannelId($adminConfig->admin_id); $is_water = WaterBookService::instance()->showBook($channel_id, $uid); [$type, $books] = BookService::instance()->getWeinxinSearchBooks($message, $is_water,$adminConfig->admin_id); //查询到图书 if ($books) { if ($type == 1) { //推荐 多本书 文字 $content = "未找到您要查询的书籍,为您推荐:\r\n\r\n"; foreach($books as $book){ $url = getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book['id']]); $content .= "{$book['name']}\r\n\r\n"; } //新增一个最近阅读 //$uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig->admin_id, $message->FromUserName); if ($uid) { $soruceReadLog = BookService::instance()->getUserRecentlyRead()->setConnect($uid)->getRecentlyRead(0, 3, $uid, true); if ($soruceReadLog['totalNum'] > 0) { $book_info = current($soruceReadLog['data']); $cacheKey = CacheConstants::getBookSubCache($uid); //如果点下一章关注,阅读记录加一章 if (Redis::instance()->get($cacheKey) == $book_info['book_id']) { $chapterInfo = BookService::instance()->getChapterInfo($book_info['book_id'], $book_info['chapter_id'] + 1)->data; BookService::instance()->setRecentlyRead($chapterInfo['name'], $chapterInfo['id'], $book_info['book_id'], $chapterInfo['idx'], $uid); $book_info['chapter_name'] = $chapterInfo['name']; } $recentBookUrl = getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book_info['book_id']]); $title = "阅读记录,你上次看到了\r\n《%s》 %s \r\n\r\n"; $title = sprintf($title, $book_info['book_name'], $book_info['chapter_name']); $content.=$title; $content .= "".("【点此继续阅读】").""; } } $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_SEARCH_BOOKS; $object->msg_content = $content; return $object; // return new Text($content); } else { //单本书图文 $book = $books[0]; $items = []; array_push($items, [ 'title' => $book['name'], 'url' => getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book['id']]), 'image' => $book['image'], 'description' => $book['description'] ?? '' ]); $object->msg_type = MarkConstants::MSG_TYPE_NEWS; $object->msg_mark = MarkConstants::MARK_SEARCH_BOOKS; $object->msg_content = $items; return $object; // return new News($items); } } //未查询到结果的情况 $content = BookService::instance()->getEventTextNoResult($adminConfig->admin_id); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_SEARCH_NO_BOOKS; $object->msg_content = $content; return $object; // return $content; } return ''; } /** * @param AdminConfigObject $adminConfig * @param EventObject $message * @param $userId * @return bool|Text * @throws \Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function eventSubscribe(AdminConfigObject $adminConfig, EventObject $message) { $countKey = 'SC:' . $adminConfig->admin_id . ':' . date('Ymd', time()); //记录当天该渠道商的导粉数量 $resdata = Redis::instance()->incr($countKey); if ($resdata == 1) { Redis::instance()->expire($countKey, 86400); //设置失效时间为一天 } $eventKey = explode('_', $message->EventKey); //检查是否是永久二维码关注 if ($eventKey && $eventKey[0] == 'qrscene' && count($eventKey) == 2) { $message->EventKey = $eventKey[1]; $replayData = $this->weChatPermanentQRCodeFollow($adminConfig, $message); if ($replayData) { return $replayData; } } //判断公众号是否关闭了自动回复 $switch = AdminService::instance()->getSubscribeSwitchCache($adminConfig->admin_id); if (empty($switch) || $switch['status'] == 2) { return ''; } return $this->autoReply($message->ToUserName, $message->FromUserName, $message->Event); } /** * 用户取消订阅 * @param $channelId * @param $openid * @throws \Exception */ public function eventUnsubscribe($channelId, $openid) { $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channelId, $openid); $userKey = CacheConstants::getUserCacheKey($userId); if ($userId) { $oldUser = UserService::instance()->getUserModel()->getUserInfo($userId); if (!$oldUser) { LogService::info('用户不存在'); return; } $upUserData = []; if ($oldUser['is_first_unfollow']) { if ($oldUser['is_first_unfollow'] == UserConstants::USER_IS_FIRST_UNFOLLOW_NEVER) { $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_YES; } elseif ($oldUser['is_first_unfollow'] == UserConstants::USER_IS_FIRST_UNFOLLOW_YES) { $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_NO; } } else { $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_NO; } //取消关注时,清空关注时的推广链接 $upUserData['follow_referral_id'] = 0; $oAna = new AnalysisObject(); $oAna->type = KafkaDotConstants::TYPE_UNSUBSCRIBE; $oAna->event_time = Request::instance()->server('REQUEST_TIME'); //发送打点数据 $dotData = new DotObject(); $dotData->bind($upUserData); $dotData->channel_id = $channelId; $dotData->user_id = $userId; $dotData->is_first_unfollow = $upUserData['is_first_unfollow']; $dotData->event_time = Request::instance()->server('REQUEST_TIME'); $dotData->action_type = MqConstants::ROUTING_KEY_UNSUBSCRIBE; $dotData->type = MqConstants::MSG_TYPE_UNFOLLOW; MqService::instance()->sendMessage($dotData); //取消关注推广链接打点,推广链接净关注使用,使用用户关注时的推广链接进行打点 if ($user_follow_referral_id = $oldUser['follow_referral_id'] ?? null) { $oAna->user_from = [ 'referral_id' => $user_follow_referral_id ]; $dotData->action_type = MqConstants::ROUTING_KEY_UNSUBSCRIBE_REFERRAL; $dotData->referral_id = $user_follow_referral_id; MqService::instance()->sendMessage($dotData); } $oAna->data['user_collect'] = 1; KafkaDotService::instance()->sendMsg($userId, $oAna); //取消关注打点 $upUserData['is_subscribe'] = UserConstants::USER_IS_SUBSCRIBE_NO; UserService::instance()->update($upUserData, ['id' => $userId]); //更新关注状态 if (Redis::instance()->exists($userKey)) { Redis::instance()->hmset($userKey, $upUserData); Redis::instance()->expire($userKey, 86400); } } } /** * 微信永久二维码关注回复 * @param AdminConfigObject $adminConfig 渠道商信息 * @param EventObject $message 微信信息 * @param bool $is_scan 是不是已关注扫码 * @return Text|mixed * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException * @throws \think\Exception */ protected function weChatPermanentQRCodeFollow(AdminConfigObject $adminConfig, EventObject $message,$is_scan = false) { $object = new ReplyMsgObject(); $index = $message->EventKey; $qrcode = CustomService::instance()->getCustomQrcodeModel()->field('id,type,eventkey,book_name,book_id,chapter_id,referral_id')->where(['admin_id' => $adminConfig->admin_id, 'index' => $index])->find(); if ($qrcode) { $cacheKey = CacheConstants::getQrcodeVisitCache($message->FromUserName); Redis::instance()->set($cacheKey, $qrcode['id'], 10); if(!$is_scan){ //UV统计 $redis = Redis::instance(); $pre_num = $redis->pfCount("QR_UV:{$adminConfig->admin_id}:{$index}"); $redis->pfAdd("QR_UV:{$adminConfig->admin_id}:{$index}",$message->FromUserName); $after_num = $redis->pfCount("QR_UV:{$adminConfig->admin_id}:{$index}"); //UV每日统计 if($after_num>$pre_num){ $redis->pfAdd("QR_UV:".date('Ymd').":{$adminConfig->admin_id}:{$index}",$message->FromUserName); $redis->expire("QR_UV:".date('Ymd').":{$adminConfig->admin_id}:{$index}",60*60*24); } } //资源回复 if (intval($qrcode['type']) == MessageConstants::QRCODE_EVENT_TYPE_RESOURCE) { if ($autoReply = $this->autoReply($message->ToUserName, $message->FromUserName, $qrcode['eventkey'], MessageConstants::MESSAGE_TYPE_EVENT_YES)) { if ($autoReply instanceof ReplyMsgObject && $autoReply->msg_mark == MarkConstants::MARK_RESOURCE) { $autoReply->msg_mark = MarkConstants::MARK_QR_RESOURCE; } return $autoReply; } } //书籍回复 if (intval($qrcode['type']) == MessageConstants::QRCODE_EVNET_TYPE_BOOK) { $replay = new ReplayTemplate($adminConfig->admin_id); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_QR_RESOURCE; $object->msg_content = $replay->getQRCodeReplayTemplate('欢迎关注', $qrcode->toArray()); return $object; // return new Text($replay->getQRCodeReplayTemplate('欢迎关注', $qrcode->toArray())); } //推广链接回复 if (intval($qrcode['type']) == MessageConstants::QRCODE_EVENT_TYPE_REFERRAL) { $ref = model('Referral')->getone($qrcode['referral_id'],false); $replay = new ReplayTemplate($adminConfig->admin_id); $html = $replay->getQRCodeRefReplayTemplate('欢迎关注', getCurrentDomain($adminConfig->admin_id,"/t/{$ref['id']}")); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_QR_RESOURCE; $object->msg_content = $html; return $object; // return new Text($html); } } return ''; } /** * 获取最近阅读 * @param $admin_id * @param $user * @return ReturnObject */ public function getLocalReading($admin_id, $user) { $object = new ReplyMsgObject(); //用户信息 if ($user) { //获取阅读历史 OR 推荐书籍 try { $soruceReadLog = BookService::instance()->getUserRecentlyRead()->setConnect($user['id'])->getRecentlyRead(0, 3, $user['id'], true); if ($soruceReadLog['totalNum'] > 0) { $book_info = current($soruceReadLog['data']); $cacheKey = CacheConstants::getBookSubCache($user['id']); //如果点下一章关注,阅读记录加一章 if (Redis::instance()->get($cacheKey) == $book_info['book_id']) { $chapterInfo = BookService::instance()->getChapterInfo($book_info['book_id'], $book_info['chapter_id'] + 1, $user['id']); if ($chapterInfo->code == ErrorCodeConstants::SUCCESS) { $chapterInfo = $chapterInfo->data; BookService::instance()->setRecentlyRead($chapterInfo['name'], $chapterInfo['id'], $book_info['book_id'], $chapterInfo['idx'], $user['id']); $book_info['chapter_name'] = $chapterInfo['name']; } } $title = "欢迎关注,你上次看到了\r\n 《%s》 %s"; $title = sprintf($title, $book_info['book_name'], $book_info['chapter_name']); $readlog = current($soruceReadLog['data']); //发送消息 $replay = new ReplayTemplate($admin_id); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_LAST_BOOK; $object->msg_content = $replay->getFollowReplayTemplate($title, $readlog, []); return $this->setData($object)->getReturn(); } } catch (\Exception $e) { LogService::exception($e); } } return $this->setData(false)->getReturn(); } /** * 获取自动回复 * @param int $admin_id 渠道ID * @param int $openid 用户OPENID * @param string $content 渠道自定义关注回复 * @return string 推送Text * @throws */ private function getSubscribe($admin_id, $openid, $content = null) { try{ $sex = UserConstants::USER_SEX_MAN; $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($admin_id, $openid); $user = UserService::instance()->getUserModel()->getUserInfo($uid); $readlog = []; if ($user) { if ($user['sex'] != UserConstants::USER_SEX_UNKNOWN) { $sex = $user['sex']; } $result = $this->getLocalReading($admin_id, $user)->data; if ($result) { return $result; } } $object = new ReplyMsgObject(); //关注回复 if ($content) { $book_name = null; $book_id = null; try { $rankList = controller('index/index')->ranklist($sex); $book_name = $rankList['idx'][0]['name']; $book_id = $rankList['idx'][0]['id']; if ($book_name && $book_id) { $book_url = getCurrentDomain($admin_id, '/index/book/chapter', ['book_id' => $book_id]); $keys = ['{$book_name}', '{$book_url}']; $book = [$book_name, $book_url]; $content = str_replace($keys, $book, $content); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_RESOURCE; $object->msg_content = $content; return $object; // return new Text($content); } } catch (\Exception $e) { LogService::error("获取推荐书籍错误"); } } $channelId = AdminService::instance()->getAdminExtendModel()->getChannelId($user['channel_id']); $isWater = WaterBookService::instance()->showBook($channelId,$uid);//清水逻辑 $recommandBook = BookService::instance()->getFollowRecommandModel()->getBooksBySex($sex,$isWater); //推荐书籍 if ($recommandBook) { $title = '亲,终于等到你了!'; //发送消息 $replay = new ReplayTemplate($admin_id); // return $replay->getFollowReplayTemplate($title, $readlog, $recommandBook); $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_RECOMMEND_BOOKS; $object->msg_content = $replay->getFollowReplayTemplate($title, $readlog, $recommandBook); return $object; } //欢迎关注 return new Text('感谢关注!'); }catch (Exception $e){ LogService::error($e->getMessage().$e->getFile().$e->getLine()); //欢迎关注 return new Text('感谢关注!'); } } /** * 自动回复匹配(自带占位符替换逻辑) * @param string $user_name 公众号user_name gh_c15958c0fa33 * @param string $openid 发送人openid * @param string $msg 用户发送原始内容 * @param int $type 消息类型 默认0消息 1事件client event * @return mixed * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ protected function autoReply($user_name, $openid, $msg, $type = MessageConstants::MESSAGE_TYPE_EVENT_NO) { $object = new ReplyMsgObject(); LogService::info('查询自动回复 公众号:' . $user_name . ' openid:' . $openid . ' msg:' . $msg); $adminConfig = AdminService::instance()->getAdminConfigModel()->getInfoByOfficialUserName($user_name); //解绑处理 if (!$adminConfig) { return false; } $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig['admin_id'], $openid); $autoReply = false; if ($type == MessageConstants::MESSAGE_TYPE_EVENT_NO) { // 自动回复消息设置 if ($msg == '$openid$') { //内置规则 查看当前访问用户openid return new Text($openid); } if ($msg == '$userid$') { $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig['admin_id'], $openid); return new Text($userId); } if ($msg == '签到') { //内置规则 签到推送消息 $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_REPLY_INNER_KEYWORDS; $object->msg_content = $this->sign($adminConfig['admin_id'], $openid); return $object; // return new Text($this->sign($adminConfig['admin_id'], $openid)); } if ($msg == 'subscribe') { $itkey = ''; $switch = AdminService::instance()->getSubscribeSwitchCache($adminConfig['admin_id']); if (!empty($switch) && $switch['status'] == 1) { if ($switch['type'] == 2) { $subcontent = AdminService::instance()->getSubscribeConfigCache($adminConfig['admin_id']); if (!empty($subcontent) && !empty($subcontent['event_keys'])) { $eventKey = json_decode($subcontent['event_keys'],true); if ($subcontent['type'] == 'news') { $itkey = $eventKey['news']; } elseif ($subcontent['type'] == 'text') { $itkey = $eventKey['text']; } } } } if (!empty($itkey)) { $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([ 'admin_id' => $adminConfig['admin_id'], 'status' => 'normal', 'eventkey' => $itkey ])->find(); } } else{ $where = []; $where['admin_id'] = $adminConfig['admin_id']; // $where['text'] = $msg; $where['status'] = 'normal'; $msg = str_replace(['\'', '"', ',', ';', '.'], ['', '', '', '', ''], $msg); if ($msg) { $autoReply = OfficialAccountsService::instance()->getWechatAutoreplyModel()->where("find_in_set('{$msg}',text)")->where($where)->find(); } if($autoReply['type'] == 'text'){ $tmpContent = json_encode($autoReply['text_content']); }else{ $tmpContent = json_encode($autoReply['news_content']); } $tmpContent = str_replace("\\","",$tmpContent); $isBlockIosUser = UserVipExtendService::instance()->isBlockIosUser($adminConfig, $uid, $tmpContent); if($isBlockIosUser){ $autoReply = []; } if ($autoReply) { //具有自动回复 $object->push_id = $autoReply['id']; if ($autoReply['eventkey']) { $event_key = $autoReply['eventkey']; } else { $event_keys = json_decode($autoReply['event_keys'], true); $event_key = $event_keys[$autoReply['type']]; } $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([ 'admin_id' => $adminConfig['admin_id'], 'eventkey' => $event_key, 'status' => 'normal', ])->find(); } } } else { // client event事件触发资源回复 if ($msg == '签到') { //内置规则 签到推送消息 $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_CLICK_INNER_KEYWORDS; $object->msg_content = $this->sign($adminConfig['admin_id'], $openid); return $object; // return new Text($this->sign($adminConfig['admin_id'], $openid)); } $result = OfficialAccountsService::instance()->getWechatResponseModel()->get([ 'admin_id' => $adminConfig['admin_id'], 'eventkey' => $msg, 'status' => 'normal', ]); } /** * 返回内容处理 */ if (isset($result) && $result) { $result['content'] = UrlService::instance()->replaceReferralHost($adminConfig['admin_id'], $result['content'], false)->data; $user = UserService::instance()->getUserModel()->getUserInfo($uid); if ($user) { $result['content'] = str_replace('$user_nickname',$user['nickname'], $result['content']); } switch ($result['type']) { case 'text': if ($msg == 'subscribe') { //关注 $object = $this->getSubscribe($adminConfig['admin_id'], $openid, $result['content']); if ($object instanceof ReplyMsgObject && $autoReply) { $object->push_id = $autoReply['id']; } return $object; } else { $object->msg_type = MarkConstants::MSG_TYPE_TEXT; $object->msg_mark = MarkConstants::MARK_REPLY_PRE_RESOURCE; $object->msg_content = $result['content']; return $object; // return new Text($result['content']); } break; case 'image': if ($msg == 'subscribe') { $data = $this->getLocalReading($adminConfig['admin_id'], $user)->data; if ($data) { return $data; } } $content = json_decode($result['content'], true); return new Image($content['media_id']); break; case 'news': if ($msg == 'subscribe') { $data = $this->getLocalReading($adminConfig['admin_id'], $user)->data; if ($data) { return $data; } } $content = json_decode($result['content'], true); $news = []; foreach ($content as $key => $value) { //if ($key == 0) { $value['image'] = cdnurl($value['image']); $content[$key] = $value; // $news[] = new NewsItem($value); $news[] = $value; //} } $object->msg_type = MarkConstants::MSG_TYPE_NEWS; $object->msg_mark = MarkConstants::MARK_RESOURCE; $object->msg_content = $news; return $object; // return new News($news); break; } } /** * 内置自动回复规则 */ switch ($msg) { case 'subscribe': // 关注 return $this->getSubscribe($adminConfig['admin_id'], $openid); break; case 'ContactManager': //联系客服 case 'manager_contact': //联系客服 if ($adminConfig && isset($adminConfig['manager_contact']) && !empty($adminConfig['manager_contact'])) { $contact = $adminConfig['manager_contact']; if (!empty($contact)) { $contact = strip_tags($contact, '
');
$contact = preg_replace("/
');
$contact = preg_replace("/