OfficialAccountsEventService.php 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Bear
  5. * Date: 2018/11/29
  6. * Time: 上午10:20
  7. */
  8. namespace app\main\service;
  9. use app\api\library\ReplayTemplate;
  10. use app\common\library\Ip;
  11. use app\common\service\SendChannelMsgService;
  12. use app\common\service\ShareService;
  13. use app\common\library\Redis;
  14. use app\common\library\WeChatObject;
  15. use app\common\service\ActiveShareService;
  16. use app\common\service\UserVipExtendService;
  17. use app\common\service\WaterBookService;
  18. use app\main\constants\CacheConstants;
  19. use app\main\constants\ErrorCodeConstants;
  20. use app\main\constants\KafkaDotConstants;
  21. use app\main\constants\MarkConstants;
  22. use app\main\constants\MessageConstants;
  23. use app\main\constants\MqConstants;
  24. use app\main\constants\OpenPlatformConstants;
  25. use app\main\constants\UserConstants;
  26. use app\main\helper\StringHelper;
  27. use app\main\model\object\AdminConfigObject;
  28. use app\main\model\object\AnalysisObject;
  29. use app\main\model\object\DotObject;
  30. use app\main\model\object\EventObject;
  31. use app\main\model\object\ReplyMsgObject;
  32. use app\main\model\object\ReturnObject;
  33. use app\main\model\object\UserObject;
  34. use EasyWeChat\Kernel\Messages\Image;
  35. use EasyWeChat\Kernel\Messages\News;
  36. use EasyWeChat\Kernel\Messages\NewsItem;
  37. use EasyWeChat\Kernel\Messages\Text;
  38. use GuzzleHttp\Client;
  39. use think\Config;
  40. use think\Exception;
  41. use think\Log;
  42. use think\Model;
  43. use think\Request;
  44. use function GuzzleHttp\Psr7\parse_query;
  45. /**
  46. * 处理公众号事件信息
  47. * Class OfficialAccountsEventService
  48. * @package app\main\service
  49. */
  50. class OfficialAccountsEventService extends BaseService
  51. {
  52. /**
  53. * @var OfficialAccountsEventService
  54. */
  55. protected static $self = NULL;
  56. /**
  57. * @var EventObject
  58. */
  59. private $wechatMsg;
  60. /**
  61. * @return OfficialAccountsEventService
  62. */
  63. public static function instance()
  64. {
  65. if (self::$self == NULL) {
  66. self::$self = new self();
  67. }
  68. return self::$self;
  69. }
  70. /**
  71. * 订阅号事件管理
  72. * @param $appId
  73. * @return \app\main\model\object\ReturnObject
  74. */
  75. public function eventForSubscription($appId)
  76. {
  77. try {
  78. $subscription = OfficialAccountsService::instance()->getSubscriptionModel()->getSubscriptionByAppId($appId);
  79. if (!$subscription || empty($subscription['refresh_token'])) {
  80. $msg = '[ WeChat ] [ Subscription ] [ API ] error: AppId:' . $appId . ' refresh_token or subscription info get fail';
  81. return $this->setCode(ErrorCodeConstants::DB_ERROR_SELECT)->setMsg($msg)->getReturn();
  82. }
  83. $refresh_token = $subscription['refresh_token'];
  84. LogService::debug("Subscription Get OfficialAccount Appid:{$appId} RefreshToken:{$refresh_token}");
  85. $officialAccount = OpenPlatformService::instance()->getOfficialAccount($appId, $refresh_token);
  86. $officialAccount->server->push(function ($message) use ($officialAccount, $subscription) {
  87. $time = Request::instance()->server('REQUEST_TIME');
  88. LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . json_encode(var_export($message, true), JSON_UNESCAPED_UNICODE));
  89. //订阅号交互管理
  90. if (isset($message['Event']) && $message['Event'] == 'unsubscribe') {
  91. //取消关注
  92. OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->unsubscribe($time);
  93. } else {
  94. //添加用户Fans
  95. OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->createFans($officialAccount, $time);
  96. }
  97. //更新粉丝的交互时间
  98. OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->updateOperateTime();
  99. //消息回复
  100. switch ($message['MsgType']) {
  101. case 'text':
  102. if ($message['Content'] == '$openid$') {
  103. return $message['FromUserName'];
  104. }
  105. if ($message['Content'] == '$userid$') {
  106. $userId = OfficialAccountsService::instance()->getSubscriptionFansModel()->setConnect($subscription['id'], $message['FromUserName'])->getFansId();
  107. return new Text($userId);
  108. }
  109. break;
  110. }
  111. return false;
  112. });
  113. $officialAccount->server->serve()->send();
  114. return $this->getReturn();
  115. } catch (\Exception $e) {
  116. return $this->getExceptionReturn($e);
  117. }
  118. }
  119. /**
  120. * 全网发布事件
  121. * @param $appId
  122. * @return \app\main\model\object\ReturnObject
  123. */
  124. public function eventForAllNet($appId)
  125. {
  126. try {
  127. $server = OpenPlatformService::instance()->getOfficialAccount($appId)->server;
  128. $server->push(function ($message) {
  129. $messageObject = (new EventObject())->bind($message);
  130. LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . $messageObject->toJson());
  131. switch ($messageObject->MsgType) {
  132. case MessageConstants::MESSAGE_TYPE_EVENT:
  133. if (in_array($messageObject->ToUserName, OpenPlatformConstants::$event_callback_user)) {
  134. return $messageObject->Event . 'from_callback';
  135. }
  136. break;
  137. case MessageConstants::MESSAGE_TYPE_TEXT: // 文本
  138. if ($messageObject->Content == 'TESTCOMPONENT_MSG_TYPE_TEXT') {
  139. return 'TESTCOMPONENT_MSG_TYPE_TEXT_callback';
  140. }
  141. if (strpos($messageObject->Content, 'QUERY_AUTH_CODE:') === 0) {
  142. $query_auth_code = str_replace('QUERY_AUTH_CODE:', '', $messageObject->Content);
  143. LogService::info('[ WeChat ] [ MP ] [ API ] QUERY_AUTH_CODE: ' . json_encode(var_export($query_auth_code,
  144. true), JSON_UNESCAPED_UNICODE));
  145. $auth = OpenPlatformService::instance()->openPlatform->handleAuthorize($query_auth_code); // 使用授权码换取接口调用凭据和授权信息
  146. LogService::info('[ WeChat ] [ MP ] [ API ] AUTH: ' . json_encode(var_export($auth, true),
  147. JSON_UNESCAPED_UNICODE));
  148. $app = OpenPlatformService::instance()->getOpenPlatform()->officialAccount($auth['authorization_info']['authorizer_appid'],
  149. $auth['authorization_info']['authorizer_refresh_token']);
  150. $app->customer_service
  151. ->message(new Text($query_auth_code . '_from_api'))
  152. ->to($messageObject->FromUserName)
  153. ->send();
  154. }
  155. break;
  156. default:
  157. break;
  158. }
  159. return true;
  160. });
  161. $server->serve()->send();
  162. return $this->getReturn();
  163. } catch (\Exception $e) {
  164. return $this->getExceptionReturn($e);
  165. }
  166. }
  167. /**
  168. * 检查平台id有效性
  169. * @param $platform_id
  170. * @return ReturnObject
  171. */
  172. public function checkOphostLegal($platform_id)
  173. {
  174. $sourceDomain = UrlService::instance()->getSourceDomain();
  175. //检测是否是用户默认平台
  176. $ophost = OfficialAccountsService::instance()->getOphostModel()->getInfoByHost($sourceDomain);
  177. if ($ophost) {
  178. //非法用户判断
  179. if (isset($ophost['platform_id']) && $platform_id != $ophost['platform_id']) {
  180. return $this->setData(false)->getReturn();
  181. }
  182. } else {
  183. return $this->setData(false)->getReturn();
  184. }
  185. return $this->setData(true)->getReturn();
  186. }
  187. /**
  188. * @param $appId
  189. * @return \app\main\model\object\ReturnObject
  190. */
  191. public function eventForNormal($appId)
  192. {
  193. try {
  194. $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId);
  195. if (!$adminConfig) {
  196. $this->setLimitCache($appId);
  197. return $this->setMsg('渠道信息无效!' . $appId)->getReturn();
  198. }
  199. $adminConfigObject = (new AdminConfigObject())->bind($adminConfig);
  200. //检测平台有效性
  201. if (!$this->checkOphostLegal($adminConfigObject->platform_id)->data) {
  202. return $this->setMsg('平台无效!' . $adminConfigObject->platform_id)->getReturn();
  203. }
  204. $refreshToken = OpenPlatformService::instance()->getRefreshToken($adminConfigObject->admin_id);
  205. $server = OpenPlatformService::instance()->getOfficialAccount($appId, $refreshToken)->server;
  206. $server->push(function ($message) use ($adminConfigObject) {
  207. $messageObject = (new EventObject())->bind($message);
  208. $this->wechatMsg = $messageObject;
  209. return $this->eventCallBack($messageObject, $adminConfigObject)->data;
  210. });
  211. $server->serve()->send();
  212. if ($this->wechatMsg->MsgType == MessageConstants::MESSAGE_TYPE_EVENT) {
  213. Log::info("EVENT_ANALYSIS:" . $this->wechatMsg->Event . "-" . $adminConfigObject->admin_id . ",time:" . (microtime(true) - THINK_START_TIME));
  214. }
  215. return $this->getReturn();
  216. } catch (\Exception $e) {
  217. return $this->getExceptionReturn($e);
  218. }
  219. }
  220. /**
  221. * 用户注册回调
  222. */
  223. public function processCallbackUserInfo($appId)
  224. {
  225. $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId);
  226. if ($adminConfig) {
  227. $list = Redis::instance()->hGet("callback_user", $adminConfig['admin_id']);
  228. Redis::instance()->hDel('callback_user', $adminConfig['admin_id']);
  229. if ($list) {
  230. $list = json_decode($list, true);
  231. foreach ($list as $message) {
  232. $channel_id = $adminConfig['admin_id'];
  233. $messageObj = (new EventObject())->bind($message);
  234. $userId = UserService::instance()->weChatInsertUser($channel_id, $messageObj->FromUserName)->data;
  235. if ($userId) {
  236. $cacheKey = CacheConstants::getQrcodeVisitCache($messageObj->FromUserName);
  237. if ($id = Redis::instance()->get($cacheKey)) {
  238. $oAna = new AnalysisObject();
  239. $oAna->type = KafkaDotConstants::TYPE_VISIT;
  240. $oAna->event_time = Request::instance()->server('REQUEST_TIME');
  241. $oAna->user_from = [
  242. 'qrcode_id' => $id
  243. ];
  244. KafkaDotService::instance()->sendMsg($userId, $oAna);
  245. }
  246. switch ($messageObj->Event) {
  247. case MessageConstants::MESSAGE_EVENT_SUBSCRIBE:
  248. $this->callbackSubscribe($channel_id, $messageObj);
  249. break;
  250. case MessageConstants::MESSAGE_EVENT_CLICK:
  251. if ($messageObj->EventKey == '签到') {
  252. $this->sendShareText($appId, $channel_id, $messageObj->FromUserName);
  253. } else{
  254. $this->sendText($appId, $channel_id, $messageObj->FromUserName);
  255. }
  256. break;
  257. }
  258. //更新用户交互时间
  259. UserService::instance()->updateUserOperateTime($userId, $messageObj->Event);
  260. }
  261. }
  262. }
  263. }
  264. }
  265. public function sendText($appId, $channel_id, $openid)
  266. {
  267. //如果Channel_id 是二维码图片菜单,则,查询回复的内容文字
  268. $adminInfo = AdminService::instance()->getAdminConfigModel()->getAdminInfoAll($channel_id);
  269. if($adminInfo['is_qrcode_menu'] == '1'){
  270. $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId);
  271. $wechat = new WeChatObject($adminConfig);
  272. $officialAccount = $wechat->getOfficialAccount();
  273. $event_key = $this->wechatMsg->EventKey;
  274. $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([
  275. 'admin_id' => $channel_id,
  276. 'eventkey' => $event_key,
  277. 'status' => 'normal',
  278. ])->find();
  279. // 点击菜单'联系客服'时,event_key='manager_contact',此时$result=null
  280. if ($result) {
  281. $text = '请点击图片,长按识别二维码,进入[' . $result->title . ']';
  282. //调用客服消息
  283. $news = new Text($text);
  284. $officialAccount->customer_service
  285. ->message($news)
  286. ->to($openid)
  287. ->send();
  288. }
  289. }
  290. }
  291. /**
  292. * 关注事件的异步处理
  293. * @param $channel_id
  294. * @param EventObject $eventObject
  295. * @return bool
  296. */
  297. public function callbackSubscribe($channel_id, EventObject $eventObject)
  298. {
  299. $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channel_id, $eventObject->FromUserName);
  300. $userKey = CacheConstants::getUserCacheKey($userId);
  301. $redis = Redis::instance();
  302. $request_time = Request::instance()->server('REQUEST_TIME');
  303. try {
  304. /*
  305. * php打点,关注
  306. */
  307. $wechat = OpenPlatformService::instance()->officialAccount;
  308. $wechat->http_client = new Client(Config::get('wechat.http'));
  309. $wechatUser = $wechat->user->get($eventObject->FromUserName); //获取微信用户
  310. if (array_key_exists('errcode', $wechatUser)) {
  311. LogService::notice("获取微信用户失败:" . json_encode($wechatUser));
  312. return false;
  313. }
  314. $updateData = new UserObject();
  315. $updateData->is_subscribe = UserConstants::USER_IS_SUBSCRIBE_YES;
  316. $updateData->avatar = $wechatUser['headimgurl'];
  317. $updateData->nickname = $wechatUser['nickname'];
  318. $updateData->sex = $wechatUser['sex'];
  319. $updateData->subscribe_time = $request_time;
  320. if ($userId) { //判断是否有此用户
  321. $oldUser = UserService::instance()->getUserModel()->getUserInfo($userId);
  322. //用户关注推广链接为空时,更新用户关注推广链接
  323. $user_follow_referral_id = $oldUser['follow_referral_id'] ?? null;
  324. $referral_id = $oldUser['referral_id'];
  325. if(!$user_follow_referral_id && $referral_id){
  326. $updateData->follow_referral_id = $referral_id;
  327. $user_follow_referral_id = $referral_id;
  328. }
  329. $oldFollowTime = $oldUser['subscribe_time'];
  330. $res = UserService::instance()->update($updateData->toArray(), ['id' => $userId]); //更新关注状态 关注时间 昵称 头像等
  331. if ($redis->exists($userKey)) {
  332. $redis->hmset($userKey, $updateData->toArray());
  333. $redis->expire($userKey, 86400);
  334. }
  335. $dotData = new DotObject();
  336. //打点信息初始化
  337. $dotData->channel_id = $channel_id;
  338. $dotData->user_id = $userId;
  339. $dotData->event_time = $request_time;;
  340. $dotData->is_first_follow = UserConstants::USER_IS_FIRST_FOLLOW_YES;
  341. //新增关注用户统计
  342. if ($oldUser && $res) {
  343. if ($oldUser['is_first_unfollow'] != UserConstants::USER_IS_FIRST_UNFOLLOW_NEVER) {
  344. $dotData->is_first_follow = UserConstants::USER_IS_FIRST_FOLLOW_NO;
  345. }
  346. //新增性别打点
  347. if ($oldUser['sex'] == UserConstants::USER_SEX_UNKNOWN && $updateData->sex) {
  348. //性别变更多打一次点
  349. $oSexAna = new AnalysisObject();
  350. $oSexAna->event_time = $request_time;
  351. $oSexAna->data['sex'] = $updateData->sex;
  352. $oSexAna->data['user_collect'] = 1;
  353. $oSexAna->type = KafkaDotConstants::TYPE_SEX;
  354. KafkaDotService::instance()->sendMsg($userId, $oSexAna);
  355. $newDotData = $dotData;
  356. $newDotData->sex = $updateData->sex;
  357. $newDotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE;
  358. $newDotData->type = MqConstants::MSG_TYPE_SEX;
  359. MqService::instance()->sendMessage($dotData);
  360. }
  361. }
  362. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE;
  363. $dotData->type = MqConstants::MSG_TYPE_FOLLOW;
  364. MqService::instance()->sendMessage($dotData);
  365. $oAna = new AnalysisObject();
  366. $oAna->event_time = $request_time;
  367. $oAna->type = [KafkaDotConstants::TYPE_SUBSCRIBE];
  368. //php打点推广链接关注,以第一次关注推广链接为准
  369. if ($user_follow_referral_id) {
  370. $oAna->user_from = [
  371. 'referral_id' => $user_follow_referral_id
  372. ];
  373. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE_REFERER;
  374. $dotData->referral_id = $user_follow_referral_id;
  375. MqService::instance()->sendMessage($dotData);
  376. }
  377. $oAna->data['user_collect'] = 1;
  378. KafkaDotService::instance()->sendMsg($userId, $oAna);
  379. if ($oldUser['is_first_unfollow'] == 2 && Config::get('site.is_open_share') == "1"){//判断是不是分享链接的用户 && 分享活动开关配置为开
  380. ShareService::instance()->attentionUser($userId,$wechatUser);
  381. }
  382. if ($oldFollowTime == 0 && $referral_id) {
  383. //首次关注回源
  384. $url = getCurrentDomain($channel_id,"/t/{$referral_id}");
  385. ReferralService::instance()->sendReferralAdData($referral_id, ['admin_id' => $channel_id, 'url' => $url, 'sub' => ['productName' => "关注"], 'openid' => $oldUser['openid']]);
  386. }
  387. }
  388. } catch (\Exception $exception) {
  389. LogService::exception($exception);
  390. if ($userId) {
  391. $upUserData = ['is_subscribe' => UserConstants::USER_IS_SUBSCRIBE_YES, 'subscribe_time' => $request_time];
  392. UserService::instance()->update($upUserData, ['id' => $userId]);
  393. if ($redis->exists($userKey)) {
  394. $redis->hmset($userKey, $upUserData);
  395. $redis->expire($userKey, 86400);
  396. }
  397. }
  398. }
  399. }
  400. /**
  401. * 更新模板回调缓存
  402. * @param $appid
  403. */
  404. public function updateTplCallback($appid)
  405. {
  406. $cacheKey = "TPL_CALLBACK_" . $appid;
  407. Redis::instance()->incr($cacheKey);
  408. Redis::instance()->expire($cacheKey, Config::get("app_callback_tpl_cache"));
  409. if (Redis::instance()->get($cacheKey) >= Config::get("app_callback_tpl_threshold")) {
  410. $this->setLimitCache($appid);
  411. }
  412. }
  413. /**
  414. * 添加限制缓存
  415. * @param $appid
  416. */
  417. public function setLimitCache($appid)
  418. {
  419. LogService::info('appid limited:' . $appid);
  420. file_put_contents(sys_get_temp_dir() . '/TPL_CALLBACK_' . $appid, time());
  421. }
  422. /**
  423. * @param $messageObject EventObject
  424. * @param $adminConfigObject AdminConfigObject
  425. * @return \app\main\model\object\ReturnObject
  426. */
  427. public function eventCallBack($messageObject, $adminConfigObject)
  428. {
  429. try {
  430. LogService::info('[ WeChat ] [ MP ] [ API ] Message: ' . $messageObject->toJson());
  431. if ($messageObject->Event == 'MASSSENDJOBFINISH') {
  432. // 高级群发JOB推送
  433. SendChannelMsgService::instance()->saveSuccessCount($messageObject->MsgID, $messageObject->SentCount, $adminConfigObject->admin_id);
  434. }
  435. if ($messageObject->Event == 'TEMPLATESENDJOBFINISH') {
  436. $this->updateTplCallback($adminConfigObject->appid);
  437. return $this->setData('')->getReturn();
  438. }
  439. if (!$messageObject->FromUserName) {
  440. LogService::error("用户信息为空");
  441. return $this->setData('')->getReturn();
  442. }
  443. //将非取消关注的事件存入缓存
  444. if ($messageObject->Event != MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE) {
  445. $data = $messageObject->toArray();
  446. $cache = Redis::instance()->hGet('callback_user', $adminConfigObject->admin_id);
  447. if ($cache) {
  448. $cache = json_decode($cache, true);
  449. $cache[] = $data;
  450. }else{
  451. $cache = [$data];
  452. }
  453. Redis::instance()->hSet("callback_user", $adminConfigObject->admin_id, json_encode($cache));
  454. }
  455. $replayData = false;
  456. switch ($messageObject->MsgType) {
  457. case MessageConstants::MESSAGE_TYPE_EVENT:
  458. switch ($messageObject->Event) {
  459. case MessageConstants::MESSAGE_EVENT_SCAN: //永久二维码已关注
  460. $replayData = $this->weChatPermanentQRCodeFollow($adminConfigObject, $messageObject, true);
  461. if (!$replayData) {
  462. $replayData = $this->getSubscribe($adminConfigObject->admin_id, $messageObject->FromUserName);
  463. }
  464. break;
  465. case MessageConstants::MESSAGE_EVENT_SUBSCRIBE: // 关注
  466. $replayData = $this->eventSubscribe($adminConfigObject, $messageObject);
  467. break;
  468. case MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE: // 取消关注
  469. //取消订阅不需要回复
  470. $this->eventUnsubscribe($adminConfigObject->admin_id, $messageObject->FromUserName);
  471. break;
  472. case MessageConstants::MESSAGE_EVENT_CLICK: //点击
  473. $replayData = $this->autoReply($messageObject->ToUserName, $messageObject->FromUserName, $messageObject->EventKey, MessageConstants::MESSAGE_TYPE_EVENT_YES);
  474. if ($replayData instanceof ReplyMsgObject && $replayData->msg_mark != MarkConstants::MARK_CLICK_INNER_KEYWORDS) {
  475. $replayData->msg_mark = MarkConstants::MARK_CLICK_PRE_RESOURCE;
  476. }
  477. break;
  478. }
  479. break;
  480. case MessageConstants::MESSAGE_TYPE_TEXT: // 文本
  481. $replayData = $this->eventText($messageObject, $adminConfigObject);
  482. break;
  483. }
  484. $replayData = $this->setMarkForReplyData($replayData, $messageObject)->data;
  485. return $this->setData($replayData)->getReturn();
  486. } catch (\Exception $e) {
  487. LogService::exception($e);
  488. return $this->setData('')->getReturn();
  489. }
  490. }
  491. /**
  492. * 添加打点内容
  493. * @param $data
  494. * @param EventObject $eventObject
  495. * @return ReturnObject|News|Text
  496. */
  497. public function setMarkForReplyData($data, EventObject $eventObject)
  498. {
  499. $appid = Request::instance()->param('appid');
  500. if (!($data instanceof ReplyMsgObject)) {
  501. return $this->setData($data)->getReturn();
  502. }else{
  503. LogService::debug("RESOURCE:".$data->toJson());
  504. }
  505. $push_id = '';
  506. if ($data->msg_mark == MarkConstants::MARK_QR_RESOURCE) {
  507. $push_id = (string)$eventObject->EventKey;
  508. }
  509. if ($data->push_id) {
  510. $push_id = (string)$data->push_id;
  511. }
  512. $push_time = time();
  513. $mark = $data->msg_mark;
  514. if ($data->msg_type == MarkConstants::MSG_TYPE_TEXT) {
  515. preg_match_all("<a href=['\"](.*?)['\"]>", $data->msg_content, $match);
  516. if ($match) {
  517. $replace = [];
  518. foreach ($match[1] as $index => $item) {
  519. $push_idx = $index + 1;
  520. $parse = parse_url($item);
  521. if (array_key_exists('host', $parse)) {
  522. if (preg_match('/^' . $appid . '/', $parse['host'])) {
  523. if (array_key_exists('query', $parse)) {
  524. $query = parse_query($parse['query']);
  525. if (array_key_exists('book_id', $query) && !$push_id) {
  526. $push_id = (string)$query['book_id'];
  527. }
  528. $item .= '&' . $this->processExt($mark, $push_id, $push_idx, $push_time);
  529. } else {
  530. $item .= '?' . $this->processExt($mark, $push_id, $push_idx, $push_time);
  531. }
  532. }
  533. }
  534. $replace[] = $item;
  535. }
  536. $data->msg_content = StringHelper::replaceOneByOne($data->msg_content, $match[1], $replace);
  537. //替换占位符
  538. if (strrpos($data->msg_content, '{ZHIDING}')) {
  539. $top = model('ManageKey')->getOneByKey('top');
  540. $data->msg_content = str_replace('{ZHIDING}', cdnurl($top['image']), $data->msg_content);
  541. }
  542. if (strrpos($data->msg_content, '{KANDIAN}')) {
  543. $kandian = Config::get('site.kandian_sign');
  544. $data->msg_content = str_replace('{KANDIAN}', $kandian, $data->msg_content);
  545. }
  546. }
  547. LogService::debug($data->msg_content);
  548. return $this->setData(new Text($data->msg_content))->getReturn();
  549. }
  550. if ($data->msg_type == MarkConstants::MSG_TYPE_NEWS) {
  551. $news = [];
  552. foreach ($data->msg_content as $index => $item) {
  553. $push_idx = $index + 1;
  554. $parse = parse_url($item['url']);
  555. if (array_key_exists('query', $parse)) {
  556. $query = parse_query($parse['query']);
  557. if (array_key_exists('book_id', $query) && !$push_id) {
  558. $push_id = (string)$query['book_id'];
  559. }
  560. $item['url'] .= '&' . $this->processExt($mark, $push_id, $push_idx, $push_time);
  561. } else {
  562. $item['url'] .= '?' . $this->processExt($mark, $push_id, $push_idx, $push_time);
  563. }
  564. $news[] = new NewsItem($item);
  565. }
  566. return $this->setData(new News($news))->getReturn();
  567. }
  568. return $this->setData($data)->getReturn();
  569. }
  570. public function processExt($mark, $push_id, $push_idx, $push_time)
  571. {
  572. $data = json_encode([
  573. 'mark' => $mark,
  574. 'push_id' => $push_id,
  575. 'push_idx' => $push_idx,
  576. 'push_time' => $push_time,
  577. ]);
  578. return 'ext=' . urlencode($data);
  579. }
  580. /**
  581. * @param EventObject $message
  582. * @param AdminConfigObject $adminConfig
  583. * @return bool|News|Text|string
  584. * @throws \Exception
  585. * @throws \think\db\exception\DataNotFoundException
  586. * @throws \think\db\exception\ModelNotFoundException
  587. * @throws \think\exception\DbException
  588. */
  589. public function eventText(EventObject $message, AdminConfigObject $adminConfig)
  590. {
  591. $object = new ReplyMsgObject();
  592. /**
  593. * 自动回复处理
  594. */
  595. $autoReply = $this->autoReply($message->ToUserName, $message->FromUserName, $message->Content);
  596. if ($autoReply) {
  597. if ($autoReply instanceof ReplyMsgObject && $autoReply->msg_mark == MarkConstants::MARK_RESOURCE){
  598. $autoReply->msg_mark = MarkConstants::MARK_REPLY_PRE_RESOURCE;
  599. }
  600. return $autoReply;
  601. }
  602. if ($adminConfig->search_tip) {
  603. $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig->admin_id, $message->FromUserName);
  604. $channel_id = AdminService::instance()->getAdminExtendModel()->getChannelId($adminConfig->admin_id);
  605. $is_water = WaterBookService::instance()->showBook($channel_id, $uid);
  606. [$type, $books] = BookService::instance()->getWeinxinSearchBooks($message, $is_water,$adminConfig->admin_id);
  607. //查询到图书
  608. if ($books) {
  609. if ($type == 1) {
  610. //推荐 多本书 文字
  611. $content = "未找到您要查询的书籍,为您推荐:\r\n\r\n";
  612. foreach($books as $book){
  613. $url = getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book['id']]);
  614. $content .= "<a href='{$url}'>{$book['name']}</a>\r\n\r\n";
  615. }
  616. //新增一个最近阅读
  617. //$uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig->admin_id, $message->FromUserName);
  618. if ($uid) {
  619. $soruceReadLog = BookService::instance()->getUserRecentlyRead()->setConnect($uid)->getRecentlyRead(0, 3, $uid, true);
  620. if ($soruceReadLog['totalNum'] > 0) {
  621. $book_info = current($soruceReadLog['data']);
  622. $cacheKey = CacheConstants::getBookSubCache($uid);
  623. //如果点下一章关注,阅读记录加一章
  624. if (Redis::instance()->get($cacheKey) == $book_info['book_id']) {
  625. $chapterInfo = BookService::instance()->getChapterInfo($book_info['book_id'], $book_info['chapter_id'] + 1)->data;
  626. BookService::instance()->setRecentlyRead($chapterInfo['name'], $chapterInfo['id'], $book_info['book_id'], $chapterInfo['idx'], $uid);
  627. $book_info['chapter_name'] = $chapterInfo['name'];
  628. }
  629. $recentBookUrl = getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book_info['book_id']]);
  630. $title = "阅读记录,你上次看到了\r\n《%s》 %s \r\n\r\n";
  631. $title = sprintf($title, $book_info['book_name'], $book_info['chapter_name']);
  632. $content.=$title;
  633. $content .= "<a href='{$recentBookUrl}'>".("【点此继续阅读】")."</a>";
  634. }
  635. }
  636. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  637. $object->msg_mark = MarkConstants::MARK_SEARCH_BOOKS;
  638. $object->msg_content = $content;
  639. return $object;
  640. // return new Text($content);
  641. } else {
  642. //单本书图文
  643. $book = $books[0];
  644. $items = [];
  645. array_push($items,
  646. [
  647. 'title' => $book['name'],
  648. 'url' => getCurrentDomain($adminConfig->admin_id,'/index/book/chapter', ['book_id' => $book['id']]),
  649. 'image' => $book['image'],
  650. 'description' => $book['description'] ?? ''
  651. ]);
  652. $object->msg_type = MarkConstants::MSG_TYPE_NEWS;
  653. $object->msg_mark = MarkConstants::MARK_SEARCH_BOOKS;
  654. $object->msg_content = $items;
  655. return $object;
  656. // return new News($items);
  657. }
  658. }
  659. //未查询到结果的情况
  660. $content = BookService::instance()->getEventTextNoResult($adminConfig->admin_id);
  661. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  662. $object->msg_mark = MarkConstants::MARK_SEARCH_NO_BOOKS;
  663. $object->msg_content = $content;
  664. return $object;
  665. // return $content;
  666. }
  667. return '';
  668. }
  669. /**
  670. * @param AdminConfigObject $adminConfig
  671. * @param EventObject $message
  672. * @param $userId
  673. * @return bool|Text
  674. * @throws \Exception
  675. * @throws \think\db\exception\DataNotFoundException
  676. * @throws \think\db\exception\ModelNotFoundException
  677. * @throws \think\exception\DbException
  678. */
  679. public function eventSubscribe(AdminConfigObject $adminConfig, EventObject $message)
  680. {
  681. $countKey = 'SC:' . $adminConfig->admin_id . ':' . date('Ymd', time()); //记录当天该渠道商的导粉数量
  682. $resdata = Redis::instance()->incr($countKey);
  683. if ($resdata == 1) {
  684. Redis::instance()->expire($countKey, 86400); //设置失效时间为一天
  685. }
  686. $eventKey = explode('_', $message->EventKey);
  687. //检查是否是永久二维码关注
  688. if ($eventKey && $eventKey[0] == 'qrscene' && count($eventKey) == 2) {
  689. $message->EventKey = $eventKey[1];
  690. $replayData = $this->weChatPermanentQRCodeFollow($adminConfig, $message);
  691. if ($replayData) {
  692. return $replayData;
  693. }
  694. }
  695. //判断公众号是否关闭了自动回复
  696. $switch = AdminService::instance()->getSubscribeSwitchCache($adminConfig->admin_id);
  697. if (empty($switch) || $switch['status'] == 2) {
  698. return '';
  699. }
  700. return $this->autoReply($message->ToUserName, $message->FromUserName, $message->Event);
  701. }
  702. /**
  703. * 用户取消订阅
  704. * @param $channelId
  705. * @param $openid
  706. * @throws \Exception
  707. */
  708. public function eventUnsubscribe($channelId, $openid)
  709. {
  710. $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channelId, $openid);
  711. $userKey = CacheConstants::getUserCacheKey($userId);
  712. if ($userId) {
  713. $oldUser = UserService::instance()->getUserModel()->getUserInfo($userId);
  714. if (!$oldUser) {
  715. LogService::info('用户不存在');
  716. return;
  717. }
  718. $upUserData = [];
  719. if ($oldUser['is_first_unfollow']) {
  720. if ($oldUser['is_first_unfollow'] == UserConstants::USER_IS_FIRST_UNFOLLOW_NEVER) {
  721. $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_YES;
  722. } elseif ($oldUser['is_first_unfollow'] == UserConstants::USER_IS_FIRST_UNFOLLOW_YES) {
  723. $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_NO;
  724. }
  725. } else {
  726. $upUserData['is_first_unfollow'] = UserConstants::USER_IS_FIRST_UNFOLLOW_NO;
  727. }
  728. //取消关注时,清空关注时的推广链接
  729. $upUserData['follow_referral_id'] = 0;
  730. $oAna = new AnalysisObject();
  731. $oAna->type = KafkaDotConstants::TYPE_UNSUBSCRIBE;
  732. $oAna->event_time = Request::instance()->server('REQUEST_TIME');
  733. //发送打点数据
  734. $dotData = new DotObject();
  735. $dotData->bind($upUserData);
  736. $dotData->channel_id = $channelId;
  737. $dotData->user_id = $userId;
  738. $dotData->is_first_unfollow = $upUserData['is_first_unfollow'];
  739. $dotData->event_time = Request::instance()->server('REQUEST_TIME');
  740. $dotData->action_type = MqConstants::ROUTING_KEY_UNSUBSCRIBE;
  741. $dotData->type = MqConstants::MSG_TYPE_UNFOLLOW;
  742. MqService::instance()->sendMessage($dotData);
  743. //取消关注推广链接打点,推广链接净关注使用,使用用户关注时的推广链接进行打点
  744. if ($user_follow_referral_id = $oldUser['follow_referral_id'] ?? null) {
  745. $oAna->user_from = [
  746. 'referral_id' => $user_follow_referral_id
  747. ];
  748. $dotData->action_type = MqConstants::ROUTING_KEY_UNSUBSCRIBE_REFERRAL;
  749. $dotData->referral_id = $user_follow_referral_id;
  750. MqService::instance()->sendMessage($dotData);
  751. }
  752. $oAna->data['user_collect'] = 1;
  753. KafkaDotService::instance()->sendMsg($userId, $oAna);
  754. //取消关注打点
  755. $upUserData['is_subscribe'] = UserConstants::USER_IS_SUBSCRIBE_NO;
  756. UserService::instance()->update($upUserData, ['id' => $userId]); //更新关注状态
  757. if (Redis::instance()->exists($userKey)) {
  758. Redis::instance()->hmset($userKey, $upUserData);
  759. Redis::instance()->expire($userKey, 86400);
  760. }
  761. }
  762. }
  763. /**
  764. * 微信永久二维码关注回复
  765. * @param AdminConfigObject $adminConfig 渠道商信息
  766. * @param EventObject $message 微信信息
  767. * @param bool $is_scan 是不是已关注扫码
  768. * @return Text|mixed
  769. * @throws \think\db\exception\DataNotFoundException
  770. * @throws \think\db\exception\ModelNotFoundException
  771. * @throws \think\exception\DbException
  772. * @throws \think\Exception
  773. */
  774. protected function weChatPermanentQRCodeFollow(AdminConfigObject $adminConfig, EventObject $message,$is_scan = false)
  775. {
  776. $object = new ReplyMsgObject();
  777. $index = $message->EventKey;
  778. $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();
  779. if ($qrcode) {
  780. $cacheKey = CacheConstants::getQrcodeVisitCache($message->FromUserName);
  781. Redis::instance()->set($cacheKey, $qrcode['id'], 10);
  782. if(!$is_scan){
  783. //UV统计
  784. $redis = Redis::instance();
  785. $pre_num = $redis->pfCount("QR_UV:{$adminConfig->admin_id}:{$index}");
  786. $redis->pfAdd("QR_UV:{$adminConfig->admin_id}:{$index}",$message->FromUserName);
  787. $after_num = $redis->pfCount("QR_UV:{$adminConfig->admin_id}:{$index}");
  788. //UV每日统计
  789. if($after_num>$pre_num){
  790. $redis->pfAdd("QR_UV:".date('Ymd').":{$adminConfig->admin_id}:{$index}",$message->FromUserName);
  791. $redis->expire("QR_UV:".date('Ymd').":{$adminConfig->admin_id}:{$index}",60*60*24);
  792. }
  793. }
  794. //资源回复
  795. if (intval($qrcode['type']) == MessageConstants::QRCODE_EVENT_TYPE_RESOURCE) {
  796. if ($autoReply = $this->autoReply($message->ToUserName, $message->FromUserName, $qrcode['eventkey'], MessageConstants::MESSAGE_TYPE_EVENT_YES)) {
  797. if ($autoReply instanceof ReplyMsgObject && $autoReply->msg_mark == MarkConstants::MARK_RESOURCE) {
  798. $autoReply->msg_mark = MarkConstants::MARK_QR_RESOURCE;
  799. }
  800. return $autoReply;
  801. }
  802. }
  803. //书籍回复
  804. if (intval($qrcode['type']) == MessageConstants::QRCODE_EVNET_TYPE_BOOK) {
  805. $replay = new ReplayTemplate($adminConfig->admin_id);
  806. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  807. $object->msg_mark = MarkConstants::MARK_QR_RESOURCE;
  808. $object->msg_content = $replay->getQRCodeReplayTemplate('欢迎关注', $qrcode->toArray());
  809. return $object;
  810. // return new Text($replay->getQRCodeReplayTemplate('欢迎关注', $qrcode->toArray()));
  811. }
  812. //推广链接回复
  813. if (intval($qrcode['type']) == MessageConstants::QRCODE_EVENT_TYPE_REFERRAL) {
  814. $ref = model('Referral')->getone($qrcode['referral_id'],false);
  815. $replay = new ReplayTemplate($adminConfig->admin_id);
  816. $html = $replay->getQRCodeRefReplayTemplate('欢迎关注', getCurrentDomain($adminConfig->admin_id,"/t/{$ref['id']}"));
  817. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  818. $object->msg_mark = MarkConstants::MARK_QR_RESOURCE;
  819. $object->msg_content = $html;
  820. return $object;
  821. // return new Text($html);
  822. }
  823. }
  824. return '';
  825. }
  826. /**
  827. * 获取最近阅读
  828. * @param $admin_id
  829. * @param $user
  830. * @return ReturnObject
  831. */
  832. public function getLocalReading($admin_id, $user)
  833. {
  834. $object = new ReplyMsgObject();
  835. //用户信息
  836. if ($user) {
  837. //获取阅读历史 OR 推荐书籍
  838. try {
  839. $soruceReadLog = BookService::instance()->getUserRecentlyRead()->setConnect($user['id'])->getRecentlyRead(0,
  840. 3, $user['id'], true);
  841. if ($soruceReadLog['totalNum'] > 0) {
  842. $book_info = current($soruceReadLog['data']);
  843. $cacheKey = CacheConstants::getBookSubCache($user['id']);
  844. //如果点下一章关注,阅读记录加一章
  845. if (Redis::instance()->get($cacheKey) == $book_info['book_id']) {
  846. $chapterInfo = BookService::instance()->getChapterInfo($book_info['book_id'], $book_info['chapter_id'] + 1, $user['id']);
  847. if ($chapterInfo->code == ErrorCodeConstants::SUCCESS) {
  848. $chapterInfo = $chapterInfo->data;
  849. BookService::instance()->setRecentlyRead($chapterInfo['name'], $chapterInfo['id'], $book_info['book_id'], $chapterInfo['idx'], $user['id']);
  850. $book_info['chapter_name'] = $chapterInfo['name'];
  851. }
  852. }
  853. $title = "欢迎关注,你上次看到了\r\n 《%s》 %s";
  854. $title = sprintf($title, $book_info['book_name'], $book_info['chapter_name']);
  855. $readlog = current($soruceReadLog['data']);
  856. //发送消息
  857. $replay = new ReplayTemplate($admin_id);
  858. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  859. $object->msg_mark = MarkConstants::MARK_LAST_BOOK;
  860. $object->msg_content = $replay->getFollowReplayTemplate($title, $readlog, []);
  861. return $this->setData($object)->getReturn();
  862. }
  863. } catch (\Exception $e) {
  864. LogService::exception($e);
  865. }
  866. }
  867. return $this->setData(false)->getReturn();
  868. }
  869. /**
  870. * 获取自动回复
  871. * @param int $admin_id 渠道ID
  872. * @param int $openid 用户OPENID
  873. * @param string $content 渠道自定义关注回复
  874. * @return string 推送Text
  875. * @throws
  876. */
  877. private function getSubscribe($admin_id, $openid, $content = null)
  878. {
  879. try{
  880. $sex = UserConstants::USER_SEX_MAN;
  881. $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($admin_id, $openid);
  882. $user = UserService::instance()->getUserModel()->getUserInfo($uid);
  883. $readlog = [];
  884. if ($user) {
  885. if ($user['sex'] != UserConstants::USER_SEX_UNKNOWN) {
  886. $sex = $user['sex'];
  887. }
  888. $result = $this->getLocalReading($admin_id, $user)->data;
  889. if ($result) {
  890. return $result;
  891. }
  892. }
  893. $object = new ReplyMsgObject();
  894. //关注回复
  895. if ($content) {
  896. $book_name = null;
  897. $book_id = null;
  898. try {
  899. $rankList = controller('index/index')->ranklist($sex);
  900. $book_name = $rankList['idx'][0]['name'];
  901. $book_id = $rankList['idx'][0]['id'];
  902. if ($book_name && $book_id) {
  903. $book_url = getCurrentDomain($admin_id, '/index/book/chapter', ['book_id' => $book_id]);
  904. $keys = ['{$book_name}', '{$book_url}'];
  905. $book = [$book_name, $book_url];
  906. $content = str_replace($keys, $book, $content);
  907. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  908. $object->msg_mark = MarkConstants::MARK_RESOURCE;
  909. $object->msg_content = $content;
  910. return $object;
  911. // return new Text($content);
  912. }
  913. } catch (\Exception $e) {
  914. LogService::error("获取推荐书籍错误");
  915. }
  916. }
  917. $channelId = AdminService::instance()->getAdminExtendModel()->getChannelId($user['channel_id']);
  918. $isWater = WaterBookService::instance()->showBook($channelId,$uid);//清水逻辑
  919. $recommandBook = BookService::instance()->getFollowRecommandModel()->getBooksBySex($sex,$isWater);
  920. //推荐书籍
  921. if ($recommandBook) {
  922. $title = '亲,终于等到你了!';
  923. //发送消息
  924. $replay = new ReplayTemplate($admin_id);
  925. // return $replay->getFollowReplayTemplate($title, $readlog, $recommandBook);
  926. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  927. $object->msg_mark = MarkConstants::MARK_RECOMMEND_BOOKS;
  928. $object->msg_content = $replay->getFollowReplayTemplate($title, $readlog, $recommandBook);
  929. return $object;
  930. }
  931. //欢迎关注
  932. return new Text('感谢关注!');
  933. }catch (Exception $e){
  934. LogService::error($e->getMessage().$e->getFile().$e->getLine());
  935. //欢迎关注
  936. return new Text('感谢关注!');
  937. }
  938. }
  939. /**
  940. * 自动回复匹配(自带占位符替换逻辑)
  941. * @param string $user_name 公众号user_name gh_c15958c0fa33
  942. * @param string $openid 发送人openid
  943. * @param string $msg 用户发送原始内容
  944. * @param int $type 消息类型 默认0消息 1事件client event
  945. * @return mixed
  946. * @throws \think\db\exception\DataNotFoundException
  947. * @throws \think\db\exception\ModelNotFoundException
  948. * @throws \think\exception\DbException
  949. */
  950. protected function autoReply($user_name, $openid, $msg, $type = MessageConstants::MESSAGE_TYPE_EVENT_NO)
  951. {
  952. $object = new ReplyMsgObject();
  953. LogService::info('查询自动回复 公众号:' . $user_name . ' openid:' . $openid . ' msg:' . $msg);
  954. $adminConfig = AdminService::instance()->getAdminConfigModel()->getInfoByOfficialUserName($user_name);
  955. //解绑处理
  956. if (!$adminConfig) {
  957. return false;
  958. }
  959. $uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig['admin_id'], $openid);
  960. $autoReply = false;
  961. if ($type == MessageConstants::MESSAGE_TYPE_EVENT_NO) { // 自动回复消息设置
  962. if ($msg == '$openid$') { //内置规则 查看当前访问用户openid
  963. return new Text($openid);
  964. }
  965. if ($msg == '$userid$') {
  966. $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($adminConfig['admin_id'], $openid);
  967. return new Text($userId);
  968. }
  969. if ($msg == '签到') { //内置规则 签到推送消息
  970. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  971. $object->msg_mark = MarkConstants::MARK_REPLY_INNER_KEYWORDS;
  972. $object->msg_content = $this->sign($adminConfig['admin_id'], $openid);
  973. return $object;
  974. // return new Text($this->sign($adminConfig['admin_id'], $openid));
  975. }
  976. if ($msg == 'subscribe') {
  977. $itkey = '';
  978. $switch = AdminService::instance()->getSubscribeSwitchCache($adminConfig['admin_id']);
  979. if (!empty($switch) && $switch['status'] == 1) {
  980. if ($switch['type'] == 2) {
  981. $subcontent = AdminService::instance()->getSubscribeConfigCache($adminConfig['admin_id']);
  982. if (!empty($subcontent) && !empty($subcontent['event_keys'])) {
  983. $eventKey = json_decode($subcontent['event_keys'],true);
  984. if ($subcontent['type'] == 'news') {
  985. $itkey = $eventKey['news'];
  986. } elseif ($subcontent['type'] == 'text') {
  987. $itkey = $eventKey['text'];
  988. }
  989. }
  990. }
  991. }
  992. if (!empty($itkey)) {
  993. $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([
  994. 'admin_id' => $adminConfig['admin_id'],
  995. 'status' => 'normal',
  996. 'eventkey' => $itkey
  997. ])->find();
  998. }
  999. }
  1000. else{
  1001. $where = [];
  1002. $where['admin_id'] = $adminConfig['admin_id'];
  1003. // $where['text'] = $msg;
  1004. $where['status'] = 'normal';
  1005. $msg = str_replace(['\'', '"', ',', ';', '.'], ['', '', '', '', ''], $msg);
  1006. if ($msg) {
  1007. $autoReply = OfficialAccountsService::instance()->getWechatAutoreplyModel()->where("find_in_set('{$msg}',text)")->where($where)->find();
  1008. }
  1009. if($autoReply['type'] == 'text'){
  1010. $tmpContent = json_encode($autoReply['text_content']);
  1011. }else{
  1012. $tmpContent = json_encode($autoReply['news_content']);
  1013. }
  1014. $tmpContent = str_replace("\\","",$tmpContent);
  1015. $isBlockIosUser = UserVipExtendService::instance()->isBlockIosUser($adminConfig, $uid, $tmpContent);
  1016. if($isBlockIosUser){
  1017. $autoReply = [];
  1018. }
  1019. if ($autoReply) { //具有自动回复
  1020. $object->push_id = $autoReply['id'];
  1021. if ($autoReply['eventkey']) {
  1022. $event_key = $autoReply['eventkey'];
  1023. } else {
  1024. $event_keys = json_decode($autoReply['event_keys'], true);
  1025. $event_key = $event_keys[$autoReply['type']];
  1026. }
  1027. $result = OfficialAccountsService::instance()->getWechatResponseModel()->where([
  1028. 'admin_id' => $adminConfig['admin_id'],
  1029. 'eventkey' => $event_key,
  1030. 'status' => 'normal',
  1031. ])->find();
  1032. }
  1033. }
  1034. }
  1035. else { // client event事件触发资源回复
  1036. if ($msg == '签到') { //内置规则 签到推送消息
  1037. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  1038. $object->msg_mark = MarkConstants::MARK_CLICK_INNER_KEYWORDS;
  1039. $object->msg_content = $this->sign($adminConfig['admin_id'], $openid);
  1040. return $object;
  1041. // return new Text($this->sign($adminConfig['admin_id'], $openid));
  1042. }
  1043. $result = OfficialAccountsService::instance()->getWechatResponseModel()->get([
  1044. 'admin_id' => $adminConfig['admin_id'],
  1045. 'eventkey' => $msg,
  1046. 'status' => 'normal',
  1047. ]);
  1048. }
  1049. /**
  1050. * 返回内容处理
  1051. */
  1052. if (isset($result) && $result) {
  1053. $result['content'] = UrlService::instance()->replaceReferralHost($adminConfig['admin_id'], $result['content'], false)->data;
  1054. $user = UserService::instance()->getUserModel()->getUserInfo($uid);
  1055. if ($user) {
  1056. $result['content'] = str_replace('$user_nickname',$user['nickname'], $result['content']);
  1057. }
  1058. switch ($result['type']) {
  1059. case 'text':
  1060. if ($msg == 'subscribe') { //关注
  1061. $object = $this->getSubscribe($adminConfig['admin_id'], $openid, $result['content']);
  1062. if ($object instanceof ReplyMsgObject && $autoReply) {
  1063. $object->push_id = $autoReply['id'];
  1064. }
  1065. return $object;
  1066. } else {
  1067. $object->msg_type = MarkConstants::MSG_TYPE_TEXT;
  1068. $object->msg_mark = MarkConstants::MARK_REPLY_PRE_RESOURCE;
  1069. $object->msg_content = $result['content'];
  1070. return $object;
  1071. // return new Text($result['content']);
  1072. }
  1073. break;
  1074. case 'image':
  1075. if ($msg == 'subscribe') {
  1076. $data = $this->getLocalReading($adminConfig['admin_id'], $user)->data;
  1077. if ($data) {
  1078. return $data;
  1079. }
  1080. }
  1081. $content = json_decode($result['content'], true);
  1082. return new Image($content['media_id']);
  1083. break;
  1084. case 'news':
  1085. if ($msg == 'subscribe') {
  1086. $data = $this->getLocalReading($adminConfig['admin_id'], $user)->data;
  1087. if ($data) {
  1088. return $data;
  1089. }
  1090. }
  1091. $content = json_decode($result['content'], true);
  1092. $news = [];
  1093. foreach ($content as $key => $value) {
  1094. //if ($key == 0) {
  1095. $value['image'] = cdnurl($value['image']);
  1096. $content[$key] = $value;
  1097. // $news[] = new NewsItem($value);
  1098. $news[] = $value;
  1099. //}
  1100. }
  1101. $object->msg_type = MarkConstants::MSG_TYPE_NEWS;
  1102. $object->msg_mark = MarkConstants::MARK_RESOURCE;
  1103. $object->msg_content = $news;
  1104. return $object;
  1105. // return new News($news);
  1106. break;
  1107. }
  1108. }
  1109. /**
  1110. * 内置自动回复规则
  1111. */
  1112. switch ($msg) {
  1113. case 'subscribe': // 关注
  1114. return $this->getSubscribe($adminConfig['admin_id'], $openid);
  1115. break;
  1116. case 'ContactManager': //联系客服
  1117. case 'manager_contact': //联系客服
  1118. if ($adminConfig && isset($adminConfig['manager_contact']) && !empty($adminConfig['manager_contact'])) {
  1119. $contact = $adminConfig['manager_contact'];
  1120. if (!empty($contact)) {
  1121. $contact = strip_tags($contact, '<p><br>');
  1122. $contact = preg_replace("/<p.*?>/is", "", $contact);
  1123. $contact = preg_replace("/<\/p>/is", "\n", $contact);
  1124. $contact = preg_replace("/<\/br>|<br>/is", "\n", $contact);
  1125. }
  1126. }
  1127. if (!isset($contact) || strlen($contact) < 7) {
  1128. $contact = Config::get('site.wx_contact');
  1129. $contact = strip_tags($contact, '<p><br>');
  1130. $contact = preg_replace("/<p.*?>/is", "", $contact);
  1131. $contact = preg_replace("/<\/p>/is", "\n", $contact);
  1132. $contact = preg_replace("/<\/br>|<br>/is", "\n", $contact);
  1133. }
  1134. return new Text($contact);
  1135. break;
  1136. }
  1137. return false;
  1138. }
  1139. /**
  1140. * 微信签到
  1141. * @param $admin_id
  1142. * @param $openid
  1143. * @return string
  1144. */
  1145. private function sign($admin_id, $openid)
  1146. {
  1147. if ($uid = OfficialAccountsService::instance()->getOpenidModel()->getUserId($admin_id, $openid)) {
  1148. [$status, $message] = OfficialAccountsService::instance()->getSignModel()->setConnect($uid)->UserSign();
  1149. if (!$status) {
  1150. LogService::info('Sign: UserID:' . $uid . ' Error:' . $message);
  1151. }
  1152. return $message;
  1153. } else {
  1154. return '签到失败,用户信息为空';
  1155. }
  1156. }
  1157. /**
  1158. * 分享活动 签到回复
  1159. * @param $appId
  1160. * @param $channel_id
  1161. * @param $openid
  1162. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  1163. * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
  1164. */
  1165. private function sendShareText($appId, $channel_id, $openid)
  1166. {
  1167. if(ActiveShareService::instance()->checkChannel($channel_id)){
  1168. $content = ActiveShareService::instance()->getSignReplyContent($channel_id);
  1169. $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoByAppId($appId);
  1170. $wechat = new WeChatObject($adminConfig);
  1171. $officialAccount = $wechat->getOfficialAccount();
  1172. //调用客服消息
  1173. $news = new Text($content);
  1174. $officialAccount->customer_service
  1175. ->message($news)
  1176. ->to($openid)
  1177. ->send();
  1178. }
  1179. }
  1180. }