123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- <?php
- /**
- * 高级群发服务接口
- * Created by: PhpStorm
- * User: lytian
- * Date: 2020/2/21
- * Time: 10:55
- */
- namespace app\main\service;
- use app\admin\service\LogService;
- use app\admin\service\ShortUrlService;
- use app\common\constants\BigData;
- use app\common\constants\Common;
- use app\common\constants\ErrorCode;
- use app\common\constants\Message;
- use app\common\library\WeChatObject;
- use app\common\model\Activity;
- use app\main\constants\ActivityConstants;
- use app\main\service\MiniProgramService;
- use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
- use EasyWeChat\Kernel\Messages\Article;
- use think\Config;
- use think\Exception;
- use think\Log;
- class HigeMessageService extends BaseService
- {
- protected static $self = null;
- private static $adminConfig = [];
- private static $wechatObj = [];
- private static $imgArr = [];
- public static function instance()
- {
- if (self::$self == null) {
- self::$self = new self();
- }
- return self::$self;
- }
- /**
- * @return \think\Model
- */
- public function getUserModel()
- {
- return model("User");
- }
- /**
- * @return \think\Model
- */
- public function getAdminConfigModel()
- {
- return model("AdminConfig");
- }
- /**
- * @return \think\Model
- */
- public function getSendMessMediaModel()
- {
- return model("SendMessMedia");
- }
- /**
- * @return \think\Model
- */
- public function getSendMessageModel()
- {
- return model("SendMessage");
- }
- /**
- * 图片转换为永久素材
- * @param $channelId
- * @param $image
- * @param bool $isThumb
- * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
- */
- public function image2media($channelId, $image, $isThumb = false)
- {
- try {
- //判断下图片
- if (strrpos($image, 'http') !== false) {
- //转换成路径
- $info = parse_url($image);
- $imgPath = trim($info['path'], '/');
- } else {
- $imgPath = trim($image, '/');
- }
- $imgPath = ROOT_PATH . 'public/' .$imgPath;
- if (!file_exists($imgPath)) {
- throw new Exception("图片文件不存在", 201);
- }
- $officeAccount = $this->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 .= "<a href=\"" . $item['url'] . "\">";
- $str .= $item['content'];
- $str .= "</a>";
- $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 = '<p><img src=\''.$c.'\'></p>';
- }
- $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];
- }
- }
|