_customModel = model('Custom'); $this->_customMediaModel = model('CustomMedia'); $this->_customMediaPushModel = model('CustomMediaPush'); $this->_customUrlModel = model('CustomUrl'); $this->_adminConfigModel = model('AdminConfig'); $this->_subscriptionModel = model('Subscription'); $this->_bookModel = model('Book'); $this->_referralModel = model('Referral'); } /** * 创建客服消息素材 * @param int $adminId 渠道商id * @param string $msgType 客服消息类型 * @param string $msgContent 文字链消息内容 * @return int|string * @throws \Exception */ public function createMedia($adminId, $msgType, $msgContent) { $media = [ 'created_admin_id' => $adminId, 'message_type' => $msgType, 'createtime' => time(), 'updatetime' => time(), ]; if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { if (!Common::isJson($msgContent)) { throw new \Exception('图文参数不是json格式'); } //检测是否有null项 有的话清理掉 if (strrpos($msgContent, 'null') !== false) { $data = json_decode($msgContent, true); $data = array_filter($data); $msgContent = json_encode($data); } $media['message_json'] = $msgContent; } elseif ($msgType == Message::MESSAGE_TYPE_LINK) { if (!Common::isJson($msgContent)) { throw new \Exception('文字链参数不是json格式'); } $media['message_text'] = $msgContent; } else { throw new \Exception('message_type不正确:' . $msgType); } $id = $this->_customMediaModel->insertGetId($media); return $id; } /** * 创建客服消息推送记录 * @param $params * @param int $msgType 客服消息类型 * @param int $officialType 公众号类型 * @param array $officialAccountIds 公众号id列表 * @param string $msgContent 消息体 * @return int|string * @throws \Exception */ public function createCustomMediaPush($params, $msgType, $officialType, $officialAccountIds, $msgContent, $other = []) { $user_json_default = json_encode(["all" => "0", "sex" => -1, "tag" => -1, "consume" => -1, "kandian" => -1, "subscribe_time" => -1], JSON_UNESCAPED_UNICODE); $model = [ 'custom_media_id' => $params['custom_media_id'] ? $params['custom_media_id'] : 0, 'message_type' => $msgType, 'official_account_type' => $officialType, 'sendtime' => strtotime($params['sendtime']), 'user_json' => $params['user_json'] ?? $user_json_default, 'created_admin_id' => $params['created_admin_id'], 'created_from' => $params['created_from'], 'createtime' => time(), 'updatetime' => time(), 'select_type' => $other['select_type'] ?? 0, 'group_id' => $other['group_id'] ?? '', ]; if ($officialType == OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SERVICE) { $model['official_account_service_ids'] = implode(',', $officialAccountIds); } elseif ($officialType == OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SUBSCRIPTION) { $model['official_account_subscribe_ids'] = implode(',', $officialAccountIds); } else { throw new \Exception('公众号类型不正确:' . $officialType); } if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { if (!Common::isJson($msgContent)) { throw new \Exception('图文参数不是json格式'); } $model['message_json'] = $msgContent; } elseif ($msgType == Message::MESSAGE_TYPE_LINK) { if (!Common::isJson($msgContent)) { throw new \Exception('文字链参数不是json格式'); } $model['message_text'] = $msgContent; } else { throw new \Exception('message_type不正确:' . $msgType); } $id = $this->_customMediaPushModel->insertGetId($model); return $id; } /** * 删除客服消息 push * @param $pushId * @return bool * @throws \Exception */ public function delCustomMediaPush($pushId) { $result = $this->_customMediaPushModel->save(['status' => Custom::CUSTOM_MEDIA_PUSH_STATUS_DELETE], ['id' => $pushId]); if ($result === false) { throw new \Exception('删除客服消息失败,push_id:' . $pushId); } $result = $this->_customModel->save(['statue' => Custom::CUSTOM_STATUE_DELETE], ['custom_media_push_id' => $pushId]); if ($result === false) { throw new \Exception('删除客服消息失败!!,push_id:' . $pushId); } return true; } /** * 更新客服消息 push * @param $pushId * @param $sendtime * @return bool * @throws \Exception */ public function editCustomMediaPush($pushId, $params) { $mediaData = $this->_customMediaPushModel->where('id', $pushId)->find(); //更新 if ($mediaData) { $sendTime = strtotime($params['sendtime']); if ($params['select_type'] == 1 && $params['group_id']) { //分组 拉取公众号ID $groupIds = trim($params['group_id'], ','); $channelRows = model("VipGroupInfo")->where('group_id', 'in', $groupIds)->field("channel_id")->select(); $channelIds = array_column($channelRows, 'channel_id'); } else { $channelIds = $params['service_id']; } //删除之前的数据 $this->_customMediaPushModel->where('id', $pushId)->delete(); $this->_customModel->where('custom_media_push_id', $pushId)->delete(); $this->_customUrlModel->where('custom_media_push_id', $pushId)->delete(); $result = $this->multiSendMessageService($mediaData['custom_media_id'], $params['sendtime'], $channelIds, $mediaData['created_admin_id'], ['group_id' => $params['group_id'], 'select_type' => $params['select_type']]); if ($result['error'] == ErrorCode::SUCCESS) { return true; } else { return false; } } return true; } /** * 创建客服消息推送 * @param array $params * @param int $msgType 消息类型 * @param string $msgContent 图文客服消息内容 * @return array * @throws \Exception */ public function createCustom($params, $msgType, $msgContent) { $sendTime = strtotime($params['sendtime']); $user_json_default = json_encode(json_encode(["all" => "0", "sex" => -1, "tag" => -1, "consume" => -1, "kandian" => -1, "subscribe_time" => -1], JSON_UNESCAPED_UNICODE), JSON_UNESCAPED_UNICODE); $custom = [ 'admin_id' => $params['admin_id'], 'created_admin_id' => $params['created_admin_id'], 'custom_media_push_id' => $params['custom_media_push_id'], 'title' => $params['title'], 'message_type' => $msgType, 'user_json' => $params['user_json'] ?? $user_json_default, 'official_account_type' => $params['official_account_type'], 'official_account_id' => $params['official_account_id'], 'sendtime' => $sendTime, 'created_from' => $params['created_from'], 'createtime' => time(), 'updatetime' => time(), 'message_json' => json_encode([]), ]; //替换占位符 $msgContent = UrlService::instance()->replaceReferralHost($params['admin_id'], $msgContent)->data; if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { $aMessageJson = json_decode($msgContent, true); } else { $custom['message_json'] = json_encode([]); $aMessageJson = json_decode($msgContent, true); } # 这里的bookIds 可能为空 $bookIds = array_column($aMessageJson, 'book_id'); $customId = $this->_customModel->allowField(true)->insertGetId($custom); if ($customId) { $pointInfo = $this->_buildPointInfo($msgType, $aMessageJson, $sendTime, $customId, $params['admin_id']); foreach ($pointInfo as $key=>&$item) { $item = UrlService::instance()->replaceReferralHost($params['admin_id'], $item)->data; } $updateData = []; $updateData = array_merge($updateData, $pointInfo); $this->_customModel->update($updateData, ['id' => $customId]); } else { Log::error('customId 未插入'); Log::error($custom); } return [$customId, $bookIds, $aMessageJson]; } #region 构造链接 /** * 构造url * @param int $channelId 渠道商id * @param array $params 参数 * @return string url * @throws \Exception */ public function buildUrl($channelId, $params) { $type = $params['type']; $url = ''; switch ($type) { case Message::MESSAGE_LINK_TYPE_BOOK: //$url = $this->_buildBookUrl($channelId, $params); $url = $this->_buildBookUrl_v2($channelId, $params); break; case Message::MESSAGE_LINK_TYPE_ACTIVITY: $url = $this->_buildActivityUrl($channelId, $params['activity_id']); break; case Message::MESSAGE_LINK_TYPE_RECENT: $url = $this->_buildRecentUrl($channelId); break; case Message::MESSAGE_LINK_TYPE_MENU: $url = $this->_buildMenuUrl($channelId, $params['menu_id']); break; case Message::MESSAGE_LINK_TYPE_NOURL: $url = ''; break; case Message::MESSAGE_LINK_TYPE_URL: $url = $params['url']; break; case Message::MESSAGE_LINK_TYPE_CUSTOMIZE_ACTIVITY: $url = $this->_buildVipActivityUrl($channelId, $params['activity_id']); break; case Message::MESSAGE_LINK_TYPE_SIGN: $url = $params['url']; break; //如果是常用链接类型,替换当前的渠道域名信息 case Message::MESSAGE_LINK_TYPE_DAILY_LINK: $dailyLink = CommonService::instance()->getTmpDailyLink(); if(isset($dailyLink[$params['url']]['link'])){ $url = getCurrentDomain($channelId,$dailyLink[$params['url']]['link'],[],$dailyLink[$params['url']]['isUseMenuDomain']); }else{ //$url = $params['url']; $url = getCurrentDomain($channelId,$params['url'],[]); } break; default: break; } return $url; } /** * 获取书籍对应的推广链接 * @param $channelId * @param $params * @return string * @throws \Exception */ private function _buildBookUrl($channelId, $params) { try { $redisService = new RedisService(); $firstChapter = $this->_bookModel->getChapterLimit($params['book_id'], 0, 0); if (empty($firstChapter)) { throw new \Exception('获取小说第一章失败,bookId:' . $params['book_id']); } $referral = [ 'type' => 1, 'book_id' => $params['book_id'], 'chapter_id' => $firstChapter['id'], 'chapter_idx' => 1, 'chapter_name' => $firstChapter['name'], 'cost' => $params['cost'], 'name' => $params['channel_name'], 'wx_type' => $params['wx_type'], 'push' => $params['push'] ?? '0', 'guide_chapter_idx' => $params['guide_chapter_idx'], 'admin_id' => $channelId, 'createtime' => time(), ]; $refId = $this->_referralModel->allowField(true)->insertGetId($referral); $sourceUrl = '/index/book/chapter?book_id=' . $referral['book_id'] . '&sid=' . $referral['chapter_id'] . '&referral_id=' . $refId; $sourceUrl = getCurrentDomain($channelId, $sourceUrl); $updateData['source_url'] = $sourceUrl; $shortUrl = new ShortUrl(); //生成腾讯短链 $updateData['short_url_qq'] = $shortUrl->tencent($channelId, $sourceUrl); //生成sina短链 $updateData['short_url_weibo'] = $shortUrl->sina($sourceUrl); $this->_referralModel->save($updateData, ['id' => $refId]); return $sourceUrl; } catch (\Exception $e) { throw $e; } } /** * 根据参数,生成阅读页链接 * @param $channelId * @param $params * @return string * @throws \Exception */ private function _buildBookUrl_v2($channelId, $params) { try { $url = ''; if ($params['type'] == Message::MESSAGE_LINK_TYPE_BOOK) { $sourceUrl = '/index/book/chapter?book_id=' . $params['book_id']; $url = getCurrentDomain($channelId, $sourceUrl); } return $url; } catch (\Exception $e) { throw $e; } } /** * 获取活动id * @param int $channelId 渠道商对应的appId * @param int $activityId 活动id * @return string */ private function _buildActivityUrl($channelId, $activityId) { $activityObj = Activity::get(['id' => $activityId]); if ($activityObj->type == ActivityConstants::ACTIVITY_TYPE_CUSTOM) { $config_id = $activityObj->config_id; $activity_config = Config::get('site.activity_config'); $url = getCurrentDomain($channelId, '/s/' . $activityId . '/r/' . $activity_config['config'][$config_id]['resource_id']); } else if ($activityObj->type == ActivityConstants::ACTIVITY_TYPE_GIVE) { $config_id = $activityObj->config_id; $activity_config = Config::get('site.activity_give_config'); $url = getCurrentDomain($channelId, '/s/' . $activityId . '/r/' . $activity_config['config'][$config_id]['resource_id']); } else { $url = getCurrentDomain($channelId, '/s/' . $activityId); } return $url; } /** * 获取VIP活动对应渠道的id * @param int $channelId 渠道商对应的id * @param int $activityId 活动id * @return string */ public function _buildVipActivityUrl($channelId, $activityId) { $url = ''; $activityObj = model("Activity")->where('id', 'eq', $activityId)->find(); if ($activityObj->type == ActivityConstants::ACTIVITY_TYPE_CUSTOM) { $config_id = $activityObj->config_id; $activity_config = Config::get('site.activity_config'); //拉取绑定关系 $row = model("VipActivityRelation") ->field("activity.id, activity.admin_id") ->join("activity", "activity.id = vip_activity_relation.slave_activity_id") ->where('vip_activity_relation.master_activity_id', 'eq', $activityId) ->where('vip_activity_relation.status', 'eq', 'normal') ->where('activity.admin_id', 'eq', $channelId) ->where('activity.status', 'eq', '1') ->where('activity.endtime', '>', time()) ->find(); if ($row) { $url = getCurrentDomain($channelId, '/s/' . $row['id'] . '/r/' . $activity_config['config'][$config_id]['resource_id']); } else { Log::info("活动不存在,channel_id: {$channelId}, activity_id: {$activityId}"); } } return $url; } /** * 获取最近阅读的链接 * @param int $channelId * @return string */ private function _buildRecentUrl($channelId) { $recentUrl = Message::getRecentFullPath($channelId); return $recentUrl; } /** * 获取目录绝对路径 * @param $channelId * @param $menuId * @return string */ private function _buildMenuUrl($channelId, $menuId) { $menuPath = Menu::$allLinks[$menuId]['menu_url']; $menuFullPath = getCurrentDomain($channelId, $menuPath); return $menuFullPath; } #endregion /** * 构造url的打点信息 * @param $msgType * @param $aMessageJson * @param $sendTime * @param $customId * @return array * @throws \Exception */ private function _buildPointInfo($msgType, $aMessageJson, $sendTime, $customId, $channelId = 0) { $customMsgJson = []; $customUpdate = []; $shortUrlService = new ShortUrlService(); if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { $mark = BigData::BIG_DATA_MARK_IMAGE_TEXT; foreach ($aMessageJson as $idx => $msgJson) { $url = $msgJson['url'] ?? ''; $url = $this->_urlBuildExt($url, $mark, $sendTime, $customId, $idx + 1); $shortUrl = $shortUrlService->create($url, $channelId); $description = ' '; if (isset($msgJson['description']) && !empty($msgJson['description'])) { $description = $msgJson['description']; } $customMsgInfo = [ 'type' => (isset($msgJson['mini_type']) && $msgJson['mini_type'] == 4) ? 11 : $msgJson['type'], 'title' => $msgJson['title'], 'url' => $shortUrl, 'image' => $msgJson['image'], 'description' => $description, ]; if ($customMsgInfo['type'] == 10 || $customMsgInfo['type'] == 11 ){//小程序类型 $mini_content = array( 'appid'=>$msgJson['appid']??'', 'page'=>$msgJson['page']??'', 'media_id'=>$msgJson['media_id']??'', 'title'=>$msgJson['title'], ); $customMsgInfo['mini_content'] = \GuzzleHttp\json_encode($mini_content,JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); } if (isset($msgJson['media_id'])){ $customMsgInfo['media_id'] = $msgJson['media_id']; } $customMsgJson[] = $customMsgInfo; } $customUpdate['message_json'] = json_encode($customMsgJson, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); } elseif ($msgType == Message::MESSAGE_TYPE_LINK) { $mark = BigData::BIG_DATA_MARK_LINK; $customMsgLink = []; foreach ($aMessageJson as $idx => $msgJson) { if (!array_key_exists('type', $msgJson) || $msgJson['type'] == Message::MESSAGE_LINK_TYPE_NOURL) { $customMsgLink[] = $msgJson['title']; } else { if($msgJson['type'] == Message::MESSAGE_LINK_TYPE_SIGN){ $customMsgLink[] = sprintf('%s', $msgJson['url'], $msgJson['title']); }else if($msgJson['type'] == Message::MESSAGE_LINK_TYPE_MINI){ $customMsgLink[] = $msgJson['url']; }else{ $url = $msgJson['url']; $url = $this->_urlBuildExt($url, $mark, $sendTime, $customId, $idx + 1); $shortUrl = $shortUrlService->create($url, $channelId); $customMsgLink[] = sprintf('%s', $shortUrl, $msgJson['title']); } } } $customUpdate['message_text'] = implode("\n\n", $customMsgLink); } return $customUpdate; } /** * 推广链接js打点相关参数 * @param $url * @param $mark * @param int $sendTime * @param int $customId * @param int $idx * @return string */ private function _urlBuildExt($url, $mark, $sendTime, $customId, $idx) { $url = trim($url); if(!strpos($url, '{$ophost}')){ // 如果 url中没有 域名占位符,不加入ext参数 $url = $this->_rejectUrlExt($url); return $url; }else{ $ext = [ 'mark' => $mark, 'push_time' => $sendTime, 'push_id' => $customId, 'push_idx' => $idx, ]; $strExt = json_encode($ext); if (strpos($url, '?') === false) { $url .= '?ext=' . $strExt; } else { $url .= '&ext=' . $strExt; } } return $url; } private function _rejectUrlExt($url) { $url_data = parse_url($url); $query = isset($url_data['query']) ? $url_data['query'] : ''; parse_str($query, $q_arr); if(!empty($q_arr['ext'])){ unset($q_arr['ext']); } $url_data['query'] = http_build_query($q_arr); $url = $this->_unparse_url($url_data); return $url; } private function _unparse_url($parsed_url) { $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; $pass = ($user || $pass) ? "$pass@" : ''; $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; return "$scheme$user$pass$host$port$path$query$fragment"; } /** * 创建统计信息 * @param $bookIds * @param $customId * @param $customMediaPushId * @param $msgType * @param $officialAccountId * @param $officialAccountType * @param $aMessageJson * @param $sendtime */ public function createCustomUrl( $bookIds, $customId, $customMediaPushId, $msgType, $officialAccountId, $officialAccountType, $aMessageJson, $sendtime ) { $aCustomUrl = []; $bookInfos = Message::getBooksInfo($bookIds); $officialAccountName = ''; if ($officialAccountType == OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SERVICE) { $configData = $this->_adminConfigModel->find($officialAccountId); if (!empty($configData)) { $officialAccountName = $configData->json['authorizer_info']['nick_name']; } } elseif ($officialAccountType == OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SUBSCRIPTION) { $subObj = $this->_subscriptionModel->getSubscriptionById($officialAccountId); if (!empty($subObj)) { $officialAccountName = $subObj['name']; } } foreach ($aMessageJson as $idx => $msgInfo) { $bookId = $msgInfo['book_id'] ?? 0; $bookId = intval($bookId); $aCustomUrl[] = [ 'custom_id' => $customId, 'idx' => $idx + 1, 'custom_media_push_id' => $customMediaPushId, 'title' => $msgInfo['title'], 'message_type' => $msgType, 'url' => $msgInfo['url'] ?? '', 'type' => $msgInfo['type'], 'official_account_id' => $officialAccountId, 'official_account_type' => $officialAccountType, 'official_account_name' => $officialAccountName, 'push_type' => $msgInfo['push'] ?? null, 'book_id' => $bookId, 'book_name' => $bookInfos[$bookId]['name'] ?? '', 'book_realname' => $bookInfos[$bookId]['realname'] ?? '', 'sendtime' => strtotime($sendtime), ]; } $this->_customUrlModel->saveAll($aCustomUrl); } /** * 图文客服消息素材库 处理群发消息中的url * @param $channel_id * @param $list * @return string * @throws \Exception */ public function processUrlForChannelId($channel_id, $list) { if ($list) { $list = json_decode($list, true); $customService = new CustomService(); foreach ($list as $index => $item) { $list[$index]['url'] = UrlService::instance()->replaceReferralHost($channel_id, $customService->buildUrl($channel_id, $item))->data; if (empty($list[$index]['url'])) { Log::info("url 不存在"); } } } else { $list = []; } return json_encode($list); } /** * @param $admin_id * @param $sub_ids * @param $open_id * @param string $data * @param int $ids * @param int $msg_type */ public function sendMessageSubscribeToUser($admin_id, $sub_ids, $open_id, $data = '', $ids = 0, $msg_type = Message::MESSAGE_TYPE_IMAGE_TEXT) { if (is_array($admin_id)) { $admin_id = array_pop($admin_id); } if (!$admin_id) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'admin_id参数错误'); } if (!$sub_ids) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'sub_id参数错误'); } if (!$open_id) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'open_id参数错误'); } try { if ($ids) { $aCustomMedia = $this->_customMediaModel->find($ids); $msg_type = $aCustomMedia['message_type']; if ($msg_type == Message::MESSAGE_TYPE_IMAGE_TEXT) { $data = $aCustomMedia['message_json']; } else { $data = $aCustomMedia['message_text']; } } if (!$data) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, '素材参数无效'); } //生成素材消息 $data = \GuzzleHttp\json_decode($data, true); if ($msg_type == Message::MESSAGE_TYPE_IMAGE_TEXT) { #region easywechat 生成图文消息 $newsArr = []; foreach ($data as $item) { $tmpArr = []; $tmpArr['title'] = $item['title']; $tmpArr['image'] = $item['image'] ?? ''; #文字链消息,没有image字段 $tmpArr['url'] = self::instance()->buildUrl($admin_id, $item); $tmpObj = new NewsItem($tmpArr); array_push($newsArr, $tmpObj); } $news = new News($newsArr); #endregion } else { #region easywechat 生成文字消息 $content = ''; foreach ($data as $item) { $url = self::instance()->buildUrl($admin_id, $item); if ($url) { $content .= ""; $content .= $item['title']; $content .= ""; } else { $content .= $item['title']; } $content .= "\n\n"; } $news = new Text($content); #endregion } $success_num = 0; foreach ($sub_ids as $sub_id) { $wechat = new WeChatObject(); $officialAccount = $wechat->getSubscriptionOfficialAccount($sub_id); if (!$officialAccount) { return LogService::toError(ErrorCode::DB_ERROR_SELECT, '无效的服务商信息'); } $result = $officialAccount->customer_service ->message($news) ->to($open_id) ->send(); if ($result['errcode'] == 0) { $success_num++; LogService::toSuccess(ErrorCode::SUCCESS, '发送成功'); } else { LogService::alert("推送消息发送失败,内容:" . json_encode($result)); } } if ($success_num) { return LogService::toSuccess(ErrorCode::SUCCESS, '发送完成'); } else { return LogService::toSuccess(ErrorCode::SUCCESS, '发送失败'); } } catch (\Exception $e) { LogService::exception($e); return LogService::toError(ErrorCode::EXCEPTION, '未知错误'); } } /** * 测试粉丝 * @param array $admin_ids 渠道id * @param int $user_id 用户id * @param string $data 素材json字符串 * @param string $ids 素材id * @param string $msg_type 消息类型 * @return array|\think\response\Json */ public function sendMessageServiceToUser(array $admin_ids, $user_id, $data = '', $ids = 0, $msg_type = Message::MESSAGE_TYPE_IMAGE_TEXT) { if (!$admin_ids || !is_array($admin_ids)) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'admin_id参数错误'); } if (!$user_id) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'user_id参数错误'); } $success_num = 0; try { if ($ids) { $aCustomMedia = $this->_customMediaModel->find($ids); $msg_type = $aCustomMedia['message_type']; if ($msg_type == Message::MESSAGE_TYPE_IMAGE_TEXT) { $data = $aCustomMedia['message_json']; } else { $data = $aCustomMedia['message_text']; } } if (!$data) { return LogService::toSuccess(ErrorCode::SUCCESS, '素材参数无效'); } foreach ($admin_ids as $admin_id) { // refresh_token adminConfig表获取 if (preg_match("/\d+/i", $user_id, $matches)) { if (!empty($matches)) { if (empty($matches)) { //没有匹配到数字。错误 return LogService::toSuccess(ErrorCode::SUCCESS, '无法匹配到此用户'); } else { $user_id = intval($matches[0]); } } } else { //没有匹配到数组,错误 return LogService::toSuccess(ErrorCode::SUCCESS, '无法匹配到此用户'); } $mUser = new User(); $mAdminConfig = new AdminConfig(); $user = $mUser->setConnect($user_id)->where('id', $user_id)->find(); if (!$user) { return LogService::toSuccess(ErrorCode::SUCCESS, '无效的用户信息'); } if ($user->channel_id != $admin_id) { continue; } $openid = $user->openid; $Config = $mAdminConfig->getAdminInfoAll($admin_id); $wechat = new WeChatObject($Config); $officialAccount = $wechat->getOfficialAccount(); if (!$officialAccount) { return LogService::toSuccess(ErrorCode::SUCCESS, '无效的服务商信息'); } $dataContent = str_replace(['$user_nickname'], [$user['nickname']], $data); $dataContent = \GuzzleHttp\json_decode($dataContent, true); $news = []; if ($msg_type == Message::MESSAGE_TYPE_IMAGE_TEXT) { #region easywechat 生成图文消息 $newsArr = []; foreach ($dataContent as $item) { if ($item['type'] == 10){ switch ($item['mini_type']){ case 3: $tempArgs = []; $tempArgs['appid'] = $item['appid'] ?? config('site.mini_appid') ; $tempArgs['title'] = $item['title']; $tempArgs['pagepath'] = $item['page']; $r = HigeMessageService::instance()->image2media($admin_id,$item['image']); $tempArgs['thumb_media_id'] = $r['media_id']; $miniObj = new MiniProgramPage($tempArgs); $result = $officialAccount->customer_service ->message($miniObj) ->to($openid) ->send(); break; case 4: $re = MiniProgramService::instance()->getMiniContent($admin_id,$item); $miniImage = new Image(\GuzzleHttp\json_decode($re,true)['media_id']); $result = $officialAccount->customer_service ->message($miniImage) ->to($openid) ->send(); break; default: break; } }else{ $tmpArr = []; $tmpArr['title'] = $item['title']; $tmpArr['description'] = $item['description'] ?: ' '; $tmpArr['image'] = $item['image'] ?? ''; #文字链消息,没有image字段 $tmpArr['url'] = self::instance()->buildUrl($admin_id, $item); $tmpObj = new NewsItem($tmpArr); } if (isset($tmpObj)){ array_push($newsArr, $tmpObj); } } if (!empty($newsArr)){ $news = new News($newsArr); } #endregion } else { #region easywechat 生成文字消息 $str_list = []; foreach ($dataContent as $item) { $url = ''; if ($item['type'] == Message::MESSAGE_LINK_TYPE_BOOK) { $sourceUrl = '/index/book/chapter?book_id='.$item['book_id']; $url = getCurrentDomain($admin_id, $sourceUrl); }elseif($item['type'] == Message::MESSAGE_LINK_TYPE_MINI){ $tempArgs = []; $tempArgs['type'] = 1; $tempArgs['page'] = trim($item['page']); $tempArgs['content'] = $item['title']; $miniStr = MiniProgramService::instance()->getMiniProgram($tempArgs,$admin_id); $str_list[] = $miniStr; continue; }else { $url = self::instance()->buildUrl($admin_id, $item); } if ($url) { $str = ''; $str .= ""; $str .= $item['title']; $str .= ""; $str_list[] = $str; } else { $str_list[] = $item['title']; } } $content = implode("\n\n", $str_list); $news = new Text($content); #endregion } if (!empty($news)){ $result = $officialAccount->customer_service ->message($news) ->to($openid) ->send(); } if ($result['errcode'] == 0) { $success_num++; LogService::toSuccess(ErrorCode::SUCCESS, '发送成功'); } else { LogService::alert("推送消息发送失败,内容:" . json_encode($result)); } } if ($success_num) { return LogService::toSuccess(ErrorCode::SUCCESS, '发送完成'); } else { return LogService::toSuccess(ErrorCode::SUCCESS, '发送失败'); } } catch (\Exception $e) { LogService::exception($e); Log::error($e); return LogService::toError(ErrorCode::EXCEPTION, '未知错误'); } } /** * 服务号群发消息 * @param int $mediaId 素材ID * @param string $sendTime 发送时间 * @param array $channelIds 服务号id * @param int $adminId 后台账号id * @return array */ public function multiSendMessageService($mediaId, $sendTime, $channelIds, $adminId, $other = []) { try { # 读取 media 信息 $media = $this->_customMediaModel::get(['id' => $mediaId])->toArray(); $msgType = $media['message_type']; if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { $msgContent = $media['message_json']; } else { $msgContent = $media['message_text']; } $params = Request::instance()->post("row/a"); return self::instance()->createPushService($sendTime, $channelIds, $adminId, $msgType, $msgContent, $mediaId, false, '', $params['user_json'], Custom::CUSTOM_CREATED_FROM_GROUP_SEND, $other); } catch (\Exception $e) { LogService::exception($e); return ['error' => ErrorCode::EXCEPTION, 'msg' => $e->getMessage()]; } } /** * 服务号群发消息 * @param int $mediaId 素材ID * @param string $sendTime 发送时间 * @param array $subIds 订阅号id * @param int $channelId 服务号id * @param int $adminId 后台账号id * @return array */ public function multiSendMessageSub($mediaId, $sendTime, $subIds, $channelId, $adminId) { try { //获取素材信息 $media = $this->_customMediaModel::get(['id' => $mediaId])->toArray(); $msgType = $media['message_type']; if ($msgType == Message::MESSAGE_TYPE_IMAGE_TEXT) { $msgContent = $media['message_json']; } else { $msgContent = $media['message_text']; } return self::instance()->createPushSub($sendTime, $subIds, $channelId, $adminId, $msgType, $msgContent, $mediaId, false, Custom::CUSTOM_CREATED_FROM_GROUP_SEND); } catch (\Exception $e) { LogService::exception($e); return ['error' => ErrorCode::EXCEPTION, 'msg' => $e->getMessage()]; } } /** * 推送订阅号 * @param string $sendTime 发送时间 * @param array $subIds 订阅号id * @param int $channelId 服务号id * @param int $adminId 后台登录账号id * @param int $msgType 消息类型 * @param string $msgContent 消息体 * @param int $mediaId 素材id * @param bool $toSave 是否保存为素材 * @param string $created_from 是否保存为素材 * @return array */ public function createPushSub($sendTime, $subIds, $channelId, $adminId, $msgType, $msgContent, $mediaId = 0, $toSave = false, $created_from = Custom::CUSTOM_CREATED_FROM_ADD) { try { if ($toSave) { $mediaId = self::instance()->createMedia($adminId, $msgType, $msgContent); } $officialType = OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SUBSCRIPTION; $mediaPushData = [ 'custom_media_id' => $mediaId, 'sendtime' => $sendTime, 'created_admin_id' => $adminId, 'created_from' => $created_from, ]; $customMediaPushId = self::instance()->createCustomMediaPush($mediaPushData, $msgType, $officialType, $subIds, $msgContent); //endregion $msgContent = self::instance()->processUrlForChannelId($channelId, $msgContent); // custom 表中记录 N 条消息 (选择了N个订阅号) foreach ($subIds as $subId) { // 服务号 $data = [ 'title' => '', 'sendtime' => $sendTime, 'admin_id' => $channelId, 'created_admin_id' => $adminId, 'custom_media_push_id' => $customMediaPushId, 'official_account_type' => $officialType, // 公众号类型:0=服务号,1=订阅号 'official_account_id' => $subId, // 公众号id 'created_from' => $created_from, ]; list($customId, $bookIds, $aMessageJson) = CustomService::instance()->createCustom($data, $msgType, $msgContent); #保存客服消息统计信息 CustomService::instance()->createCustomUrl($bookIds, $customId, $customMediaPushId, $msgType, $channelId, $officialType, $aMessageJson, $sendTime); } return ['error' => ErrorCode::SUCCESS, 'msg' => 'ok']; } catch (\Exception $e) { LogService::exception($e); return ['error' => ErrorCode::EXCEPTION, 'msg' => $e->getMessage()]; } } /** * 推送服务号 * @param string $sendTime 发送时间 * @param array $channelIds 服务号id * @param int $adminId 后台登录账号id * @param int $msgType 消息类型 * @param string $msgContent 消息体 * @param int $mediaId 素材id * @param bool $toSave 是否保存为素材 * @param string $title 消息标题 * @param string $created_from 消息标题 * @return array */ public function createPushService($sendTime, $channelIds, $adminId, $msgType, $msgContent, $mediaId = 0, $toSave = false, $title = '', $userJson = null, $created_from = Custom::CUSTOM_CREATED_FROM_ADD, $other = []) { try { if ($toSave) { $mediaId = self::instance()->createMedia($adminId, $msgType, $msgContent); } # 服务号 OR 订阅号 $officialType = OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SERVICE; $mediaPushData = [ 'custom_media_id' => $mediaId, 'sendtime' => $sendTime, 'created_admin_id' => $adminId, 'created_from' => $created_from, 'user_json' => $userJson, ]; $channelIds = array_unique($channelIds); $customMediaPushId = self::instance()->createCustomMediaPush($mediaPushData, $msgType, $officialType, $channelIds, $msgContent, $other); $ignoreArr = []; foreach ($channelIds as $channel_id) { $msgContent = self::instance()->processUrlForChannelId($channel_id, $msgContent); $jsonData = json_decode($msgContent, true); $del = []; foreach ($jsonData as $key=>$item) { if ($item['type'] == Message::MESSAGE_LINK_TYPE_CUSTOMIZE_ACTIVITY && empty($item['url'])) { //url为空 则跳过 不创建客服消息 $del[] = $key; } //如果type为url常用链接类型(type=10)&&选择的链接类型为Vip充值和小额充值,需要判断当前渠道是否有vip充值和小额充值权限 else if($item['type'] == Message::MESSAGE_LINK_TYPE_DAILY_LINK ){ //常用链接 $dailyLink = CommonService::instance()->getTmpDailyLink(); $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoAll($channel_id); //vip充值 if(strpos($item['url'],$dailyLink['vip']['link'])){ if(($adminConfig['vip_state'] ?? false) && ($adminConfig['vip_goods_id'] ?? null)){ //vip自定义商品 if(($adminconfig['vip_goods_id_custom'] ?? false)){ $item['url'] = $item['url']."&vip_custom_money=1"; } }else{ $del[] = $key; } } }//小程序 else if($item['type'] == Message::MESSAGE_LINK_TYPE_MINI){ $temMini = []; $temMini['type'] = $item['mini_type'] ?? 1; if ($temMini['type'] == 1){//文本 $temMini['page'] = $item['page']; $temMini['content'] = $item['title']; $miniContent = MiniProgramService::instance()->getMiniProgram($temMini, $adminId); $jsonData[$key]['url'] = $miniContent; }else{//图文 $con = MiniProgramService::instance()->getMiniContent($adminId,$item); $conArr = \GuzzleHttp\json_decode($con,true); $jsonData[$key] = array_merge($item,$conArr); } } } $lastContent = []; if ($del) { foreach ($jsonData as $index=>$row) { if (!in_array($index, $del)) { $lastContent[] = $row; } } } else { $lastContent = $jsonData; } if (empty($lastContent)) { $ignoreArr[] = $channel_id; continue; } $msgContent = json_encode($lastContent, JSON_UNESCAPED_UNICODE); $data = [ 'title' => $title, 'sendtime' => $sendTime, 'admin_id' => $channel_id, 'created_admin_id' => $adminId, 'custom_media_push_id' => $customMediaPushId, 'official_account_type' => $officialType, # 公众号类型:0=服务号,1=订阅号 'official_account_id' => $channel_id, # 公众号id 'created_from' => $created_from, # 公众号id 'user_json' => $userJson ? json_encode(json_encode(json_decode($userJson, true),JSON_UNESCAPED_UNICODE), true) : null, ]; list($customId, $bookIds, $aMessageJson) = CustomService::instance()->createCustom($data, $msgType, $msgContent); #保存客服消息统计信息 CustomService::instance()->createCustomUrl($bookIds, $customId, $customMediaPushId, $msgType, $channel_id, $officialType, $aMessageJson, $sendTime); } if ($ignoreArr && count($ignoreArr) == count($channelIds)) { //消息为空 删除push $this->_customMediaPushModel->where('id', 'eq', $customMediaPushId)->delete(); Log::info("所有公众号消息为空, 删除push"); return ['error' => ErrorCode::EXCEPTION, 'msg' => "您选择的公众号中存在未创建此活动的账户,请核对后重试"]; } return ['error' => ErrorCode::SUCCESS, 'msg' => 'ok']; } catch (\Exception $e) { //LogService::exception($e); Log::error($e->getMessage()); return ['error' => ErrorCode::EXCEPTION, 'msg' => $e->getMessage()]; } } /** * 获取服务号的名字 * @param $channelId * @return string * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function getServiceNameByChannelId($channelId) { $adminConfig = $this->_adminConfigModel->where(['admin_id' => $channelId])->find(); if ($adminConfig) { return $adminConfig->json['authorizer_info']['nick_name']; } else { return ''; } } /** * 获取 custom_media_push 对象 * @param $push_id custom_media_push ID * @return array */ public function getCustomMessageByPushId($push_id) { $customPushData = $this->_customMediaPushModel->getCustomInfoById($push_id); return $customPushData; } /** * 更改custom数据 * @param $customData 数据 * @param $customId custom 表 ID * @param $channelId 渠道ID */ public function updateCustomMessage($customData, $customId, $channelId) { $msgType = $customData['message_type']; if (!$msgType) { $message_arr = json_decode($customData['message_json'], true, 1024); } else { $message_arr = json_decode($customData['message_text'], true, 1024); } foreach ($message_arr as $key => $item) { if ($item['type'] == Message::MESSAGE_LINK_TYPE_MINI){//处理小程序类型 $temMini = []; $temMini['type'] = $item['mini_type'] ?? 1; if ($temMini['type'] == 1){//文本 $temMini['page'] = $item['page']; $temMini['content'] = $item['title']; $miniContent = MiniProgramService::instance()->getMiniProgram($temMini, $channelId); $message_arr[$key]['url'] = $miniContent; }else{//图文 $con = MiniProgramService::instance()->getMiniContent($channelId,$item); $conArr = \GuzzleHttp\json_decode($con,true); $message_arr[$key] = array_merge($item,$conArr); } }else{ $url = self::instance()->buildUrl($channelId, $item); //转换占位符 $message_arr[$key]['url'] = UrlService::instance()->replaceReferralHost($channelId, $url)->data; } } $pointInfo = $this->_buildPointInfo($msgType, $message_arr, $customData['sendtime'], $customId, $channelId); foreach ($pointInfo as $key=>&$item) { $item = UrlService::instance()->replaceReferralHost($channelId, $item)->data; } $customData = array_merge($customData, $pointInfo); $result = $this->_customModel->allowField(true)->save($customData, ['id' => $customId]); return $result; } /** * 获取发送成功|失败的订阅号数据 * @param $pushId custom_media_push 表ID * @param int $statue 1 => 发送成功 0=> 发送失败 * @return array * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function getOfficialSubList($pushId, $statue = Custom::CUSTOM_STATUE_HIDDEN) { $push_data = $this->_customMediaPushModel->get(['id' => $pushId])->toArray(); $data = $this->_customModel ->alias('t') ->join('subscription t1', 't.official_account_id = t1.id', 'left') ->where('t.custom_media_push_id', '=', $pushId) ->where('t.statue', '=', $statue) ->where('t.official_account_id', 'in', $push_data['official_account_subscribe_ids']) ->field('t1.name') ->select(); $list = []; if ($data) { foreach ($data as $key => $item) { $list[] = $item['name']; } } return $list; } /** * 获取发送成功|失败的订阅号数据 * @param $pushId custom_media_push 表ID * @param int $statue 1 => 发送成功 0=> 发送失败 * @return array * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function getOfficialServiceList($pushId, $statue = Custom::CUSTOM_STATUE_HIDDEN) { $push_data = $this->_customMediaPushModel->get(['id' => $pushId])->toArray(); $data = $this->_customModel ->alias('t') ->join('admin_config t1', 't.official_account_id = t1.admin_id', 'left') ->where('t.custom_media_push_id', '=', $pushId) ->where('t.statue', '=', $statue) ->where('t.official_account_id', 'in', $push_data['official_account_service_ids']) ->field('t1.json') ->select(); $list = []; if ($data) { foreach ($data as $key => $item) { $json = json_decode($item->json, true); $list[] = $json['authorizer_info']['nick_name']; } } return $list; } /** * @param $officialType * @param $pushId * @param $statue * @return array * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function getOfficialPushStatusList($officialType, $pushId, $statue) { if ($officialType == OfficialAccount::OFFICIAL_ACCOUNT_TYPE_SERVICE) { return self::instance()->getOfficialServiceList($pushId, $statue); } else { return self::instance()->getOfficialSubList($pushId, $statue); } } public function getCustomMediaDetail($id, $image_type = Message::MESSAGE_TYPE_IMAGE_TEXT) { $obj = $this->_customMediaModel->get(['id' => $id]); if($image_type == Message::MESSAGE_TYPE_IMAGE_TEXT){ $message_json = json_decode($obj->message_json, true, 512); }else{ $message_json = json_decode($obj->message_text, true, 512); } foreach($message_json as $index=>$item){ switch ($message_json[$index]['type']) { case Message::MESSAGE_LINK_TYPE_MENU: $message_json[$index]['book_name'] = Menu::$allLinks[$message_json[$index]['menu_id']]['title']; break; case Message::MESSAGE_LINK_TYPE_ACTIVITY: $activity = model('Activity') ->where('id', $message_json[$index]['activity_id'])->find(); $message_json[$index]['book_name'] = $activity['name']; break; case Message::MESSAGE_LINK_TYPE_CUSTOMIZE_ACTIVITY: $activity = model('Activity') ->where('id', $message_json[$index]['activity_id'])->find(); $message_json[$index]['book_name'] = $activity['name']; break; } } return array("total" => count($message_json), "rows" => $message_json); } public function getCustomMediaPushDetail($id, $image_type = Message::MESSAGE_TYPE_IMAGE_TEXT) { $obj = $this->_customMediaPushModel->get(['id' => $id]); if($image_type == Message::MESSAGE_TYPE_IMAGE_TEXT){ $message_json = json_decode($obj->message_json, true, 512); }else{ $message_json = json_decode($obj->message_text, true, 512); } foreach($message_json as $index=>$item){ switch ($message_json[$index]['type']) { case Message::MESSAGE_LINK_TYPE_MENU: $message_json[$index]['name'] = Menu::$allLinks[$message_json[$index]['menu_id']]['title']; break; case Message::MESSAGE_LINK_TYPE_ACTIVITY: $activity = model('Activity') ->where('id', $message_json[$index]['activity_id'])->find(); $message_json[$index]['name'] = $activity['name']; break; } } return array("total" => count($message_json), "rows" => $message_json); } }