getWechatObj($channelId)->getOfficialAccount(); if ($isThumb) { $res = $officeAccount->material->uploadThumb($imgPath); } else { $res = $officeAccount->material->uploadImage($imgPath); } if (isset($res['errcode'])) { Log::error("图文上传失败:res:". json_encode($res, JSON_UNESCAPED_UNICODE) . "channel_id: {$channelId} image: {$image}"); } return $res; } catch (Exception | InvalidArgumentException $e) { Log::error("图片素材处理失败:errormsg: {$e->getMessage()} channel_id: {$channelId} image: {$imgPath}"); return ['errcode' => 201, 'errormsg' => $e->getMessage()]; } } /** * 删除微信历史消息 * @param $channelId * @param $msgId * @return array */ public function delWechatHistoryMessage($channelId,$msgId) { try { $officeAccount = $this->getWechatObj($channelId)->getOfficialAccount(); $res = $officeAccount->broadcasting->delete($msgId); Log::info('删除微信历史消息'.json_encode($res, JSON_UNESCAPED_UNICODE) . "wx_msg_id: ".$msgId); if ($res['errmsg'] != 'ok') { Log::error("删除微信历史消息失败:res:". json_encode($res, JSON_UNESCAPED_UNICODE) . "msgId: {$msgId}"); } return $res; }catch (Exception | InvalidArgumentException $e){ Log::error("删除微信历史消息失败:errormsg: {$e->getMessage()} msgId: {$msgId}"); return ['errcode' => 201, 'errormsg' => $e->getMessage()]; } } /** * 创建图文消息 * @param $adminInfo * @param $data * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string * @throws Exception * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException */ public function uploadNews($channelId, $data) { $officeAccount = $this->getWechatObj($channelId)->getOfficialAccount(); $res = $officeAccount->material->uploadArticle($data); if (isset($res['errcode'])) { Log::error("图文上传失败:res:". json_encode($res, JSON_UNESCAPED_UNICODE) . "channel_id: {$channelId} data:". json_encode($data, JSON_UNESCAPED_UNICODE)); return $res; } else { return $res; } } /** * 创建媒体库 * @param $adminId * @param $msgType * @param $msgContent * @return int|string * @throws \Exception */ public function createMedia($adminId, $msgType, $msgContent) { $media = [ 'admin_id' => $adminId, 'type' => $msgType, 'createtime' => time(), 'updatetime' => time(), ]; 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); } if ($msgType == 1) { //文本 $media['message_text'] = $msgContent; } elseif ($msgType == 2) { //图文 $media['message_json'] = $msgContent; } elseif ($msgType == 3) { //图片 $media['message_img'] = $msgContent; } $id = $this->getSendMessMediaModel()->allowField(true)->insertGetId($media); return $id; } /** * 消息预览接口 * @param array $channelIds 测试发送的渠道 * @param int $userId 接收用户id * @param int $msgType 1 文字 2 图文 3 图片 * @param array $message 消息结构 * @param array $others 其他参数 * @return array * @throws InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ public function sent(array $channelIds, int $userId, int $msgType, array $message, array $others = []) { if (!$channelIds || !is_array($channelIds)) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'channel_id参数错误'); } if (!$userId) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, 'user_id参数错误'); } if (!$message) { return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, '消息内容无效'); } $success = 0; try { $user = $this->getUserModel()->setConnect($userId)->where('id', $userId)->find(); if (!$user) { return LogService::toError(ErrorCode::DB_ERROR_SELECT, '无效的用户信息'); } foreach ($channelIds as $channelId) { //匹配出用户ID if (preg_match("/\d+/i", $userId, $matches)) { if (!empty($matches)) { if (empty($matches)) { //没有匹配到数字。错误 return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, '无法匹配到此用户'); } else { $userId = intval($matches[0]); } } } else { //没有匹配到数组,错误 return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, '无法匹配到此用户'); } if ($user->channel_id != $channelId) { return LogService::toError(ErrorCode::DB_ERROR_SELECT, '该渠道无法匹配到此用户'); } $officialAccount = $this->getWechatObj($channelId)->getOfficialAccount(); if (!$officialAccount) { return LogService::toError(ErrorCode::DB_ERROR_SELECT, '无效的服务号信息'); } if ($msgType == 1) { //文字消息 $content = ''; $str_list = []; foreach ($message as $item) { if (isset($item['url'])) { $str = ''; $str .= ""; $str .= $item['content']; $str .= ""; $str_list[] = $str; } else { if (isset($item['page'])){ $item['content'] = MiniProgramService::instance()->getMiniText($item['page'],$item['content'],$channelId); } $str_list[] = $item['content']; } } $content = implode("\n", $str_list); Log::info('高级群发消息content:'.$content); $res = $officialAccount->broadcasting->previewText($content, $user['openid']); if (isset($res['errcode']) && $res['errcode'] != 0) { throw new Exception("高级群发测试发送失败:" . $res['errmsg'], 201); } $success++; } elseif ($msgType == 2) { //图文消息 $newsCommentData = $newsData = $originData = []; $messageCount = count($message); foreach ($message as $k => $item) { $this->__setMiniBatch($item,$channelId); $article = [ 'title' => $item['title'], 'thumb_media_id' => '', 'digest' => '', 'auth' => '', 'show_cover' => 0, 'content' => $this->parseContentImage($channelId, $item['content']), 'source_url' => '', ]; if ($item['cover']) { $coverResult = $this->image2media($channelId, $item['cover'], 1); $article['thumb_media_id'] = $coverResult['media_id']; } else { throw new Exception("缺少封面图", 201); } //if ($messageCount > 1) { $article['digest'] = (isset($item['digest']) && !empty($item['digest'])) ? $item['digest'] : mb_substr(strip_tags($item['content']), 0, 64); //} $url = ''; if (isset($item['type'])) { $url = $this->buildUrl($channelId, $item); if (in_array($item['type'], [1, 3]) && empty($url)) { return LogService::toError(ErrorCode::DB_ERROR_SELECT, '活动状态失效,请重新配置'); } } $article['source_url'] = $url; $originData[] = $article; $articleObj = new Article($article); if ($item['need_open_comment'] == 1) { $articleObj->need_open_comment = 1; $articleObj->only_fans_can_comment = $article['only_fans_can_comment'] ?? 0; } $newsCommentData[] = $articleObj; } $mediaResult = $officialAccount->material->uploadArticle($newsCommentData); if (!empty($mediaResult['errcode']) && $mediaResult['errcode'] == 88000) { foreach ($originData as $article) { $newsData[] = new Article($article); } $mediaResult = $officialAccount->material->uploadArticle($newsData); } if (isset($mediaResult['media_id'])) { $mediaId = $mediaResult['media_id']; $res = $officialAccount->broadcasting->previewNews($mediaId, $user['openid']); if (isset($res['errcode']) && $res['errcode'] != 0) { throw new Exception("高级群发测试发送失败:" . $res['errmsg'], 201); } } else { throw new Exception("高级群发测试发送失败:" . $mediaResult['errmsg'], 201); } $success++; } elseif ($msgType == 3) { //图片消息 $imgPath = current($message); $mediaResult = $this->image2media($channelId, $imgPath['img_path']); if (isset($mediaResult['media_id'])) { $mediaId = $mediaResult['media_id']; $res = $officialAccount->broadcasting->previewImage($mediaId, $user['openid']); if (isset($res['errcode']) && $res['errcode'] != 0) { throw new Exception("高级群发测试发送失败:" . $res['errmsg'], 201); } } else { throw new Exception("高级群发测试发送失败:" . $mediaResult['errmsg'], 201); } $success++; } } } catch (Exception $e) { LogService::error("高级群发:error: {$e->getMessage()} 错误line: {$e->getLine()}"); return LogService::toError(ErrorCode::EXCEPTION, '未知错误'); } if($success>0){ return LogService::toSuccess(ErrorCode::SUCCESS, '发送完成'); }else{ return LogService::toError(ErrorCode::PARAMS_ERROR_INVALID, '发送失败'); } } /** * 处理内容中的图片 * @param $channelId * @param $content * @return mixed * @throws Exception * @throws InvalidArgumentException */ private function parseContentImage($channelId, $content) { $imgPaths = []; if (preg_match_all("/src=['|\"]([^\s]*)['|\"]/i", $content, $m)) { $imgPaths = $m[1]; } if ($imgPaths) { $mediaArr = []; foreach ($imgPaths as $k => $imgPath) { // if (is_file($imgPath) && file_exists($imgPath)) { $res = $this->image2media($channelId, $imgPath); $mediaArr[$k] = $res['url']; // } } $content = str_replace($imgPaths, $mediaArr, $content); } return $content; } /** * 返回链接地址 * @param $channelId * @param $params * @return string */ public function buildUrl($channelId, $params) { $type = $params['type']; $url = ''; switch ($type) { case 0: $url = $this->_buildBookUrl($channelId, $params); break; case 1: $url = $this->_buildActivityUrl($channelId, $params); break; case 2: $url = $this->_buildRecentUrl($channelId); break; case 3: $url = $this->_buildCampaignUrl($channelId, $params); break; default: break; } return $url; } /** * 处理小程序 */ public function __setMiniBatch(&$item,$channelId) { \app\main\service\LogService::info('图文消息-小程序1'.$item['content']); if (!isset($item['mini']) || empty($item['mini']) ){ return ''; } $miniContent = $item['mini']; foreach ($miniContent as $k =>$value){ if ($value['type'] == 3){//小程序卡片 $res = $this->image2media($channelId, $value['pic']); $value['pic'] = $res['url']; } $c = MiniProgramService::instance()->getMiniProgram($value,$channelId); if ($value['type'] == 4 && $c){//小程序码 $c = '

'; } $item['content'] = str_replace( $value['tag'], $c,$item['content']); } // print_r($item['content']);exit; \app\main\service\LogService::info('图文消息-小程序'.$item['content']); unset($miniContent); } /** * 根据参数,生成阅读页链接 * @param $channelId * @param $params * @return string * @throws \Exception */ private function _buildBookUrl($channelId, $params) { try { $url = ''; if ($params['type'] == Message::MESSAGE_LINK_TYPE_BOOK) { $sourceUrl = '/index/book/chapter?book_id=' . $params['book_id'] . '&chapter_id=' . $params['chapter_id']; $url = getCurrentDomain($channelId, $sourceUrl); } return $url; } catch (\Exception $e) { throw $e; } } /** * 返回渠道活动地址 * @param $channelId * @param $params * @param $message * @return string * @throws \think\exception\DbException */ private function _buildActivityUrl($channelId, $params) { $activityObj = Activity::get(['id' => $params['activity_id']]); if (($activityObj->status != '1') || ($activityObj->endtime <= time())) { //活动无效 LogService::info("活动状态无效 channel_id:{$channelId} params:" . json_encode($params)); return ""; } $url = ''; if ($activityObj->type == ActivityConstants::ACTIVITY_TYPE_CUSTOM) { $config_id = $activityObj->config_id; $activity_config = Config::get('site.activity_config'); //判断是否是VIP创建的活动 if ($activityObj->admin_id != $channelId) { //拉取绑定关系 $row = model("VipActivityRelation") ->field("activity.id, activity.admin_id, activity.status, activity.endtime") ->join("activity", "activity.id = vip_activity_relation.slave_activity_id") ->where('vip_activity_relation.master_activity_id', 'eq', $activityObj->id) ->where('vip_activity_relation.status', 'eq', 'normal') ->where('activity.admin_id', 'eq', $channelId) ->find(); if ($row) { if (($row->status != '1') || ($row->endtime <= time())) { //活动无效 LogService::info("活动状态无效 channel_id:{$row['admin_id']} activity_id: {$row['id']} params:" . json_encode($params)); return ""; } $url = getCurrentDomain($channelId, '/s/' . $row['id'] . '/r/' . $activity_config['config'][$config_id]['resource_id']); } else { LogService::error("活动不存在,channel_id: {$channelId}, activity_id: {$params['activity_id']}"); } } else { $url = getCurrentDomain($channelId, '/s/' . $activityObj->id . '/r/' . $activity_config['config'][$config_id]['resource_id']); } } elseif($activityObj->type == ActivityConstants::ACTIVITY_TYPE_PLAT){ $url = getCurrentDomain($channelId, '/s/' . $activityObj->id); } elseif($activityObj->type == ActivityConstants::ACTIVITY_TYPE_GIVE) { $config_id = $activityObj->config_id; $activity_config = Config::get('site.activity_give_config'); $url = getCurrentDomain($channelId, '/s/' . $activityObj->id . '/r/' . $activity_config['config'][$config_id]['resource_id']); } return $url; } /** * 获取消耗活动的链接 * @param $channelId * @param $params * @return string */ private function _buildCampaignUrl($channelId, $params) { $url = '/index/recharge/campaignIndex?active_id='.$params['campaign_match_id']; $activityUrl = getCurrentDomain($channelId, $url, [], true); return $activityUrl; } /** * 获取最近阅读的链接 * @param int $channelId * @return string */ private function _buildRecentUrl($channelId) { $recentUrl = Message::getKeepReadingFullPath($channelId); return $recentUrl; } /** * 构造url的打点信息 * @param $channelId * @param $msgType * @param $url * @param $sendTime * @param $channelMessageId * @param $idx * @return string * @throws \Exception */ public function buildPointInfo($channelId, $msgType, $url, $sendTime, $channelMessageId, $idx) { //占位符替换 $url = UrlService::instance()->replaceReferralHost($channelId, $url)->data; $shortUrlService = new ShortUrlService(); if ($msgType == 1) { $mark = BigData::BIG_DATA_MARK_HIGH_MSG_TEXT; } elseif ($msgType == 2) { $mark = BigData::BIG_DATA_MARK_HIGH_MSG_IMG_TXT; } $url = $this->_urlBuildExt($url, $mark, $sendTime, $channelMessageId, $idx + 1); $shortUrl = $shortUrlService->create($url, $channelId); return $shortUrl; } /** * 推广链接js打点相关参数 * @param $url * @param $mark * @param $sendTime * @param $channelMessageId * @param $idx * @return string */ private function _urlBuildExt($url, $mark, $sendTime, $channelMessageId, $idx) { $url = trim($url); if (!strpos($url, '{$ophost}')) { // 如果 url中没有 域名占位符,不加入ext参数 $url = $this->_rejectUrlExt($url); return trim($url, '?'); } else { $ext = [ 'mark' => $mark, 'push_time' => $sendTime, 'push_id' => $channelMessageId, 'push_idx' => $idx, ]; $strExt = json_encode($ext); if (strpos($url, '?') === false) { $url .= '?ext=' . $strExt; } else { $url .= '&ext=' . $strExt; } } return trim($url, '?'); } /** * @param $url * @return string */ 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; } /** * @param $parsed_url * @return string */ 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 $adminId * @return mixed */ private function getAdminConfig($channelId) { if (!isset(self::$adminConfig[$channelId])) { self::$adminConfig[$channelId] = $this->getAdminConfigModel()->getAdminInfoAll($channelId); } return self::$adminConfig[$channelId]; } /** * 返回渠道信息 * @param $channelId * @return WeChatObject */ public function getWechatObj($channelId): WeChatObject { if (!isset(self::$wechatObj[$channelId])) { $adminConfig = $this->getAdminConfig($channelId); self::$wechatObj[$channelId] = new WeChatObject($adminConfig); } return self::$wechatObj[$channelId]; } }