企微短剧业务系统

MaterialService.php 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. <?php
  2. namespace App\Service;
  3. use App\Libs\getid3\Duration;
  4. use App\Log;
  5. use App\Models\AuthorizeCorp;
  6. use App\Models\MassMsg;
  7. use App\Models\MassMsgLinkRecord;
  8. use App\Models\Material;
  9. use App\Models\PlatformPlayletGroup;
  10. use App\Models\PlatformPlayletGroupDetail;
  11. use App\Models\RadarCustomerDetail;
  12. use App\Support\EmailQueue;
  13. use Illuminate\Support\Facades\Storage;
  14. class MaterialService
  15. {
  16. /**
  17. * 上传素材
  18. * */
  19. public static function uploadMaterial($corpid, $type, $file, $needMediaId, $uploadToOss, &$data)
  20. {
  21. try{
  22. $mediaId = '';
  23. $expireTime = '';
  24. if (empty($file) || !$file->isValid()) {
  25. return 2311;
  26. }
  27. $originalExtension = strtolower($file->getClientOriginalExtension()); //文件扩展名
  28. if(!$originalExtension) $originalExtension = $file->guessExtension();
  29. $realPath = $file->getRealPath(); //临时文件的绝对路径
  30. $mimeType = $file->getClientMimeType(); // image/jpeg
  31. $fileSize = $file->getClientSize();
  32. switch ($type) {
  33. case 1: // 图片
  34. if (!in_array($originalExtension, ['jpg', 'png'])) {
  35. Log::logError('上传的图片类型为:', ['type' => $originalExtension, 'info' => $file->guessExtension() ], 'uploadMaterial');
  36. return 2301;
  37. }
  38. if($fileSize > 10 * 1024 * 1024) {
  39. return 2306;
  40. }
  41. if($fileSize > 2 * 1024 * 1024) {
  42. $uploadToOss = 1;
  43. }
  44. list($width, $height, $imageType, $attr) = getimagesize($realPath);
  45. if($width * $height > 1555200) {
  46. return 2312;
  47. }
  48. break;
  49. case 2: // 语音
  50. if (!in_array($originalExtension, ['amr'])) {
  51. return 2302;
  52. }
  53. if($fileSize > 2 * 1024 * 1024) {
  54. return 2307;
  55. }
  56. break;
  57. case 3: // 视频
  58. if (!in_array($originalExtension, ['mp4'])) {
  59. return 2303;
  60. }
  61. # 视频文件大小限制
  62. if($fileSize > 20 * 1024 * 1024) {
  63. return 2308;
  64. }
  65. break;
  66. case 4:
  67. # 文件大小限制20M
  68. if($fileSize > 20 * 1024 * 1024) {
  69. return 2318;
  70. }
  71. break;
  72. }
  73. // 上传文件
  74. $filename = date('YmdHis') . '-' . uniqid() . '.' . $originalExtension;
  75. // 使用我们新建的uploads本地存储空间(目录)
  76. $result = Storage::disk('uploads')->put($filename, file_get_contents($realPath));
  77. if(!$result)
  78. return 2304;
  79. $localFilePath = '../storage/uploads/'.$filename;
  80. if(!$uploadToOss && $type==1) { // 上传到腾讯
  81. $fileUrl = MaterialService::uploadImg($corpid, $realPath);
  82. } else {
  83. $oss = new OssService('playlet');
  84. $ossFile = $oss->upload($originalExtension, $localFilePath, $mimeType.'/'.date("Y-m-d",time()));
  85. $fileUrl = $ossFile['oss-request-url'];
  86. }
  87. // if($needMediaId) { // 需要获取素材mediaId
  88. // $mediaData = MaterialService::uploadMedia($corpid, $localFilePath, $type);
  89. // if(isset($mediaData['errcode']) && $mediaData['errcode']) {
  90. // EmailQueue::rPush('获取素材mediaId失败', json_encode($mediaData, JSON_UNESCAPED_UNICODE), ['xiaohua.hou@kuxuan-inc.com'], '获取素材mediaId失败');
  91. // Log::logError('素材上传失败', $mediaData, 'UploadMaterial');
  92. // return 2309;
  93. // }
  94. //
  95. // $mediaId = isset($mediaData['media_id']) ? $mediaData['media_id'] : '';
  96. // $expireTime = time();
  97. // if(!empty($mediaId))
  98. // $expireTime = date('Y-m-d H:i:s', $mediaData['created_at'] + 86400 * 3 - 200);
  99. // }
  100. // @unlink($localFilePath);
  101. // 保存数据到素材表
  102. $materialId = Material::insertGetId([
  103. 'corpid' => $corpid,
  104. 'type' => $type,
  105. 'local_path' => $localFilePath,
  106. 'oss_url' => $fileUrl,
  107. 'media_id' => $mediaId,
  108. 'expire_at' => $expireTime
  109. ]);
  110. if(!$materialId) return 2310;
  111. $data = array_filter([
  112. 'material_id' => $materialId,
  113. 'url' => $fileUrl,
  114. 'media_id' => $mediaId
  115. ]);
  116. } catch (\Exception $e) {
  117. EmailQueue::rPush('素材上传过程发生异常', $e->getTraceAsString(), ['xiaohua.hou@kuxuan-inc.com'], '素材上传过程发生异常');
  118. Log::logError('素材上传过程发生异常', [
  119. 'line' => $e->getLine(),
  120. 'msg' => $e->getMessage()
  121. ], 'UploadMaterial-Exception');
  122. return 2305;
  123. }
  124. return 0;
  125. }
  126. /**
  127. * 上传朋友圈素材
  128. * */
  129. public static function uploadAttachment($corpid, $localFilePath, $type, $attachmentType, $retry=0)
  130. {
  131. # 获取AccessToken
  132. $accessToken = AuthorizeCorp::getAccessToken($corpid, '上传临时素材');
  133. if(empty($accessToken)) {
  134. return false;
  135. }
  136. $requestUri = config('qyWechat.upload_attachment');
  137. switch ($type) {
  138. case 1: // 图片
  139. $requestUri .= $accessToken . '&media_type=image&attachment_type='.$attachmentType;
  140. break;
  141. case 2: // 普通文件
  142. $requestUri .= $accessToken . '&media_type=file&attachment_type='.$attachmentType;
  143. break;
  144. case 3: // 视频
  145. $requestUri .= $accessToken . '&media_type=video&attachment_type='.$attachmentType;
  146. break;
  147. }
  148. $response = HttpService::httpUpload($requestUri, $localFilePath);
  149. Log::logError('请求的URL:'.$requestUri, [
  150. 'path' => $localFilePath,
  151. 'response' => $response,
  152. 'retry' => $retry
  153. ], 'HttpUploadAttachment');
  154. $responseData = json_decode($response, true);
  155. $errCode = $responseData['errcode'] ?? -1;
  156. if(($response===false || $errCode != 0) && $retry<5) { // 发起重试
  157. $retry++;
  158. Log::logInfo('上传朋友圈素材发起重试', [
  159. 'corpid' => $corpid,
  160. 'path' => $localFilePath,
  161. 'retry' => $retry
  162. ], 'HttpUploadAttachment');
  163. return MaterialService::uploadAttachment($corpid, $localFilePath, $type, $attachmentType, $retry);
  164. }
  165. if($response===false) { // 发起重试
  166. return false;
  167. }
  168. return $responseData;
  169. }
  170. /**
  171. * 上传临时素材
  172. * */
  173. public static function uploadMedia($corpid, $localFilePath, $type, $retry=0)
  174. {
  175. # 获取AccessToken
  176. $accessToken = AuthorizeCorp::getAccessToken($corpid, '上传临时素材');
  177. if(empty($accessToken)) {
  178. return false;
  179. }
  180. $requestUri = config('qyWechat.upload_media');
  181. switch ($type) {
  182. case 1: // 图片
  183. $requestUri .= $accessToken . '&type=image';
  184. break;
  185. case 2: // 语音
  186. $requestUri .= $accessToken . '&type=voice';
  187. break;
  188. case 3: // 视频
  189. $requestUri .= $accessToken . '&type=video';
  190. break;
  191. case 4: // 文件
  192. $requestUri .= $accessToken . '&type=file';
  193. break;
  194. }
  195. $response = HttpService::httpUpload($requestUri, $localFilePath);
  196. Log::logError('请求的URL:'.$requestUri, [
  197. 'corpid' => $corpid,
  198. 'path' => $localFilePath,
  199. 'response' => $response,
  200. 'retry' => $retry
  201. ], 'HttpUploadMedia');
  202. $responseData = json_decode($response, true);
  203. $errCode = $responseData['errcode'] ?? -1;
  204. if(($response===false || $errCode != 0) && $retry<5) { // 发起重试
  205. $retry++;
  206. Log::logInfo('上传临时素材发起重试', [
  207. 'corpid' => $corpid,
  208. 'path' => $localFilePath,
  209. 'retry' => $retry
  210. ], 'HttpUploadMedia');
  211. return MaterialService::uploadMedia($corpid, $localFilePath, $type, $retry);
  212. }
  213. return $responseData;
  214. }
  215. /**
  216. * 处理附件中的素材ID
  217. * */
  218. public static function mediaIdReplace($attachments, $corpid, $prefix = 'WelcomeCodeSendTrace')
  219. {
  220. if(empty($attachments)) return [];
  221. foreach ($attachments as $key=>&$attachment) {
  222. # 处理附件信息中的素材id
  223. if(isset($attachment['image']['media_id'])) { // 非小程序类的附件
  224. $materialId = $attachment['image']['media_id'];
  225. $mediaId = MaterialService::getMediaId($materialId, $corpid);
  226. if($mediaId===false) {
  227. Log::logInfo('【图片类】捕获到临时素材ID异常', [
  228. 'attachment' => $attachments,
  229. 'corpid' => $corpid,
  230. 'media_id' => $materialId
  231. ], $prefix);
  232. unset($attachments[$key]);
  233. continue;
  234. }
  235. $attachment['image']['media_id'] = $mediaId;
  236. } elseif(isset($attachment['miniprogram']['pic_media_id'])) {
  237. $materialId = $attachment['miniprogram']['pic_media_id'];
  238. $mediaId = MaterialService::getMediaId($materialId, $corpid);
  239. if($mediaId===false) {
  240. Log::logInfo('【小程序】捕获到临时素材ID异常', [
  241. 'attachment' => $attachments,
  242. 'corpid' => $corpid,
  243. 'media_id' => $materialId
  244. ], $prefix);
  245. unset($attachments[$key]);
  246. continue;
  247. }
  248. $attachment['miniprogram']['pic_media_id'] = $mediaId;
  249. }
  250. }
  251. Log::logInfo('处理附件中的素材ID:', [
  252. 'corpid' => $corpid,
  253. 'attachments' => $attachments
  254. ], $prefix);
  255. return $attachments;
  256. }
  257. /**
  258. * 处理附件中的素材ID
  259. * */
  260. public static function chatGroupWelcomeTemplateMediaIdReplace($attachments, $corpid, $prefix = 'WelcomeCodeSendTrace')
  261. {
  262. if(empty($attachments)) return [];
  263. foreach ($attachments as $key=>&$attachment) {
  264. # 处理附件信息中的素材id
  265. if(isset($attachments['image']['media_id'])) { // 非小程序类的附件
  266. $materialId = $attachments['image']['media_id'];
  267. $mediaId = MaterialService::getMediaId($materialId, $corpid);
  268. if($mediaId===false) {
  269. Log::logInfo('【图片类】捕获到临时素材ID异常', [
  270. 'attachment' => $attachments,
  271. 'corpid' => $corpid,
  272. 'media_id' => $materialId
  273. ], $prefix);
  274. unset($attachments[$key]);
  275. continue;
  276. }
  277. $attachments['image']['media_id'] = $mediaId;
  278. } elseif(isset($attachments['miniprogram']['pic_media_id'])) {
  279. $materialId = $attachments['miniprogram']['pic_media_id'];
  280. $mediaId = MaterialService::getMediaId($materialId, $corpid);
  281. if($mediaId===false) {
  282. Log::logInfo('【小程序】捕获到临时素材ID异常', [
  283. 'attachment' => $attachments,
  284. 'corpid' => $corpid,
  285. 'media_id' => $materialId
  286. ], $prefix);
  287. unset($attachments[$key]);
  288. continue;
  289. }
  290. $attachments['miniprogram']['pic_media_id'] = $mediaId;
  291. } elseif(isset($attachments['file']['media_id'])){
  292. $materialId = $attachments['file']['media_id'];
  293. $mediaId = MaterialService::getMediaId($materialId, $corpid, false, 4);
  294. if($mediaId===false) {
  295. Log::logInfo('【文件类】捕获到临时素材ID异常', [
  296. 'attachment' => $attachments,
  297. 'corpid' => $corpid,
  298. 'media_id' => $materialId
  299. ], $prefix);
  300. unset($attachments[$key]);
  301. continue;
  302. }
  303. $attachments['file']['media_id'] = $mediaId;
  304. } elseif (isset($attachments['video']['media_id'])) { // 非小程序类的附件
  305. $materialId = $attachments['video']['media_id'];
  306. $mediaId = MaterialService::getMediaId($materialId, $corpid, false, 3);
  307. if($mediaId===false) {
  308. Log::logInfo('【视频类】捕获到临时素材ID异常', [
  309. 'attachment' => $attachments,
  310. 'corpid' => $corpid,
  311. 'media_id' => $materialId
  312. ], $prefix);
  313. unset($attachments[$key]);
  314. continue;
  315. }
  316. $attachments['video']['media_id'] = $mediaId;
  317. }
  318. }
  319. Log::logInfo('处理附件中的素材ID:', [
  320. 'corpid' => $corpid,
  321. 'attachments' => $attachments
  322. ], $prefix);
  323. return $attachments;
  324. }
  325. /**
  326. * 处理附件中的雷达和推广链接信息
  327. * @param array $attachments 附件信息
  328. * @param string $corpid 企业id
  329. * @param string $userId 客服id
  330. * @param int $channel 渠道标识 1:朋友圈 2:群发消息 3:欢迎语 4:渠道活码
  331. * @param int $ruleId 对应渠道的规则id。用于个人时可设置为0
  332. * @param int $msgSendType 消息类型 0未知 1群发消息 2欢迎语
  333. * @param string $state 授权链接中可携带的参数
  334. * @return mixed
  335. * */
  336. public static function radarAttachment($attachments, $corpid, $userId, $channel, $ruleId, $msgSendType=0, $isAttachment=false, $state='STATE')
  337. {
  338. try {
  339. if(empty($attachments)) return [];
  340. foreach ($attachments as $key=>&$attachment) {
  341. # 检验是否存在雷达类附件
  342. $msgType = $attachment['msgtype'] ?? '';
  343. if(empty($msgType)) {
  344. unset($attachments[$key]);
  345. continue;
  346. }
  347. if($msgType == 'radar') {
  348. Log::logInfo('检测到待处理的雷达附件', [
  349. 'corpid' => $corpid,
  350. 'rule_id' => $ruleId,
  351. 'channel' => $channel,
  352. 'attachment' => $attachment
  353. ], 'radarAttachmentDealTrace');
  354. $attachment['msgtype'] = 'link';
  355. $radarId = $attachment['radar']['radar_id'] ?? 0;
  356. # 拼装雷达消息内容
  357. $linkData = RadarService::getLinkMsgOfRadar($radarId, $corpid, $userId, $channel, $ruleId, $state);
  358. Log::logInfo('雷达消息转链接消息的结果为:', [
  359. 'corpid' => $corpid,
  360. 'rule_id' => $ruleId,
  361. 'channel' => $channel,
  362. 'link_data' => $linkData
  363. ], 'radarAttachmentDealTrace');
  364. if(empty($linkData)) {
  365. Log::logInfo('未获取到雷达信息', [
  366. 'attachment' => $attachments,
  367. 'corpid' => $corpid
  368. ], 'RadarAttachmentDealTrace');
  369. unset($attachments[$key]);
  370. continue;
  371. }
  372. if($isAttachment) {
  373. $linkData['media_id'] = $attachment['radar']['media_id'];
  374. }
  375. $attachment['link'] = $linkData;
  376. unset($attachment['radar']);
  377. Log::logInfo('最终替换完成的雷达转链接消息体为:', [
  378. 'corpid' => $corpid,
  379. 'rule_id' => $ruleId,
  380. 'channel' => $channel,
  381. 'attachment' => $attachment
  382. ], 'radarAttachmentDealTrace');
  383. } elseif ($msgType == 'promote') {
  384. Log::logInfo('检测到待处理的推广链接', [
  385. 'corpid' => $corpid,
  386. 'rule_id' => $ruleId,
  387. 'channel' => $channel,
  388. 'attachment' => $attachment
  389. ], 'PromoteAttachmentDealTrace');
  390. $attachment['msgtype'] = 'link';
  391. # 获取推广链接
  392. $jumpUrl = $attachment['promote']['jump_url'] ?? '';
  393. if(empty($jumpUrl)) {
  394. Log::logInfo('未查询到推广链接信息', [
  395. 'attachment' => $attachments,
  396. 'corpid' => $corpid
  397. ], 'PromoteAttachmentDealTrace');
  398. unset($attachments[$key]);
  399. continue;
  400. }
  401. # 判断是否是剧集组推广链接。通过剧集组id、发送类型send_type确认
  402. $playletGroupId = getUrlParams($jumpUrl, 'group_id');
  403. $sendType = getUrlParams($jumpUrl, 'send_type');
  404. Log::logInfo('剧集组id相关信息', [
  405. 'group_id' => $playletGroupId,
  406. 'send_type' => $sendType,
  407. 'msg_send_type' => $msgSendType
  408. ], 'PromoteAttachmentDealTrace');
  409. # 如果有就获取对应的剧集推广链接进行存储
  410. if($playletGroupId && $sendType && $sendType==6 && $msgSendType) { // 剧集组对应剧生成推广链接
  411. $jumpUrl .='&sender='.$userId.'&rule_id='.$ruleId.'&msg_type='.$msgSendType;
  412. Log::logInfo('跳转链接更新为:', [
  413. 'jump_url' => $jumpUrl,
  414. ], 'PromoteAttachmentDealTrace');
  415. $hasRecord = false;
  416. if($msgSendType == 2) { // 欢迎语消息类型,优先判断是否已有短剧记录信息
  417. $hasRecord = MassMsgLinkRecord::where('corpid', $corpid)->where('sender', $userId)
  418. ->where('rule_id', $ruleId)->where('msg_type', $msgSendType)
  419. ->where('enable', 1)->exists();
  420. }
  421. if(!$hasRecord) {
  422. # 查询剧集组对应创建者信息
  423. $groupInfo = PlatformPlayletGroup::where('id', $playletGroupId)->where('enable', 1)->first();
  424. # 查询剧集组对应的剧信息
  425. $playletList = PlatformPlayletGroupDetail::where('group_id', $playletGroupId)->where('enable', 1)->get();
  426. $pidList = [];
  427. foreach ($playletList as $playlet) {
  428. $errno = 0;
  429. $playletData = PlatformService::createLink(
  430. $playlet, $playlet['platform_id'], $playlet['account_id'], $groupInfo->sys_group_id,
  431. $groupInfo->admin_id, $errno
  432. );
  433. if(empty($playletData) || $errno) {
  434. Log::logInfo('生成链接失败:', [
  435. 'jump_url' => $jumpUrl,
  436. 'errno' => $errno,
  437. 'playlet_data' => $playletData,
  438. 'group_id' => $playletGroupId
  439. ], 'PromoteAttachmentDealTrace');
  440. EmailQueue::rPush('群发消息生成推广链接失败', $playletGroupId.'-'.$errno.'-'.$ruleId, ['xiaohua.hou@kuxuan-inc.com'], '群发消息生成推广链接失败');
  441. continue;
  442. }
  443. array_push($pidList, $playletData['pid']);
  444. # 记录对应推广信息
  445. $recordResult = MassMsgLinkRecord::saveRecord(
  446. $corpid, $userId, $ruleId, $msgSendType, $playletGroupId, $playlet['playlet_id'],
  447. $playlet['platform_id'], $playlet['link_type'], $playlet['sort_order'], $playletData['path'],
  448. $playletData['desc'], $playletData['cover'], $playletData['pid']
  449. );
  450. if(!$recordResult) {
  451. Log::logError('剧集组推广信息记录失败:', [
  452. 'corpid' => $corpid,
  453. 'rule_id' => $ruleId,
  454. 'user_id' => $userId,
  455. 'playlet' => $playlet,
  456. 'link_data' => $playletData
  457. ], 'PlayletLinkRecordSave');
  458. }
  459. }
  460. if($msgSendType == 1) { // 更新群发消息的pid字段
  461. MassMsg::where('id', $ruleId)->update(['is_draw' => 1, 'pids' => json_encode(array_filter($pidList))]);
  462. }
  463. }
  464. }
  465. # 处理推广链接
  466. $link = MassMsgService::getPromoteLink($corpid, $jumpUrl);
  467. $linkData = $attachment['promote'];
  468. $linkData['url'] = $link;
  469. $attachment['link'] = $linkData;
  470. unset($attachment['promote']);
  471. Log::logInfo('最终替换完成的推广链接消息体为:', [
  472. 'corpid' => $corpid,
  473. 'rule_id' => $ruleId,
  474. 'channel' => $channel,
  475. 'attachment' => $attachment
  476. ], 'PromoteAttachmentDealTrace');
  477. }
  478. }
  479. } catch (\Exception $e) {
  480. Log::logError('处理附件中的雷达信息', [
  481. 'line' => $e->getLine(),
  482. 'msg' => $e->getMessage(),
  483. 'attachments' => $attachments,
  484. 'is_attachment' => $isAttachment
  485. ], 'RadarAttachmentDeal-Exception');
  486. return [];
  487. }
  488. return $attachments;
  489. }
  490. public static function promoteAttachments($corpid, $attachments, $prefix = 'addWelcomeTemplate') {
  491. if (empty($attachments)) return [];
  492. try{
  493. if(isset($attachments['promote'])) {
  494. $attachment = $attachments['promote'];
  495. Log::logInfo('检测到待处理的推广链接', [
  496. 'attachment' => $attachment
  497. ], $prefix.'Trace');
  498. # 获取推广链接
  499. $jumpUrl = $attachment['url'] ?? '';
  500. if (empty($jumpUrl)) {
  501. Log::logInfo('未查询到推广链接信息', [
  502. 'attachment' => $attachments,
  503. ], $prefix.'Trace');
  504. unset($attachments['promote']);
  505. }
  506. # 处理推广链接
  507. $link = MassMsgService::getPromoteLink($corpid, $jumpUrl);
  508. $attachment['url'] = $link;
  509. $attachments['link'] = $attachment;
  510. unset($attachments['promote']);
  511. Log::logInfo('最终替换完成的推广链接消息体为:', [
  512. 'corpid' => $corpid,
  513. 'attachment' => $attachments
  514. ], $prefix.'Trace');
  515. }
  516. } catch (\Exception $exception) {
  517. Log::logError('处理附件中的雷达信息', [
  518. 'line' => $exception->getLine(),
  519. 'msg' => $exception->getMessage(),
  520. 'attachments' => $attachments,
  521. ], $prefix.'-Exception');
  522. return [];
  523. }
  524. return $attachments;
  525. }
  526. /**
  527. * 处理附件中的素材ID(新)
  528. * */
  529. public static function mediaIdDeal($attachments, $corpid)
  530. {
  531. if(empty($attachments)) return [];
  532. foreach ($attachments as $key=>&$attachment) {
  533. $msgType = $attachment['msgtype'] ?? '';
  534. switch($msgType) {
  535. case 'image':
  536. $type = 1;
  537. $attachmentType = 1;
  538. $materialId = $attachment['image']['media_id'];
  539. break;
  540. case 'video':
  541. $type = 3;
  542. $attachmentType = 1;
  543. $materialId = $attachment['video']['media_id'];
  544. break;
  545. case 'link':
  546. $type = 1;
  547. $attachmentType = 1;
  548. $materialId = $attachment['link']['media_id'];
  549. break;
  550. case 'radar':
  551. $type = 1;
  552. $attachmentType = 1;
  553. $radarId = $attachment['radar']['radar_id'];
  554. $materialId = RadarCustomerDetail::where('id', $radarId)->where('enable', 1)->value('cover_id');
  555. break;
  556. default:
  557. $materialId = '';
  558. break;
  559. }
  560. if(empty($materialId)) {
  561. Log::logError('捕获到非预期的附件类型', [
  562. 'corpid' => $corpid,
  563. 'attachment' => $attachment
  564. ], 'mediaIdDeal');
  565. unset($attachments[$key]);
  566. continue;
  567. }
  568. Log::logInfo('附件id获取', [
  569. 'material_id' => $materialId,
  570. 'corpid' => $corpid,
  571. 'type' => $type,
  572. 'attachment_type' => $attachmentType
  573. ], 'MediaIdDeal');
  574. $mediaId = MaterialService::getMediaId($materialId, $corpid, true, $type, $attachmentType);
  575. if($mediaId===false) {
  576. EmailQueue::rPush('获取附件mediaId失败', json_encode($attachment, JSON_UNESCAPED_UNICODE), ['xiaohua.hou@kuxuan-inc.com'], '获取附件mediaId失败');
  577. Log::logInfo('获取附件素材ID发生异常', [
  578. 'attachment' => $attachment,
  579. 'corpid' => $corpid,
  580. 'type' => $type,
  581. 'attachment_type' => $attachmentType
  582. ], 'MediaIdDeal');
  583. unset($attachments[$key]);
  584. continue;
  585. }
  586. $attachment[$msgType]['media_id'] = $mediaId;
  587. }
  588. return $attachments;
  589. }
  590. /**
  591. * 下载素材并获取临时素材ID
  592. * */
  593. public static function getMediaId($materialId, $corpid, $forceRefresh=false, $type=1, $attachmentType=false)
  594. {
  595. try {
  596. $materialInfo = Material::select(['oss_url', 'media_id', 'expire_at', 'local_path', 'corpid'])
  597. ->where('id', $materialId)->where('type', $type)->first();
  598. Log::logInfo('数据库中获取的附件信息', [
  599. 'material_id' => $materialId,
  600. 'corpid' => $corpid,
  601. 'type' => $type,
  602. 'attachment_type' => $attachmentType,
  603. 'info' => $materialInfo->toArray()
  604. ], 'MediaIdDeal');
  605. if(empty($materialInfo)) {
  606. return false;
  607. }
  608. if($materialInfo->corpid != $corpid) {
  609. $materialModel = new Material;
  610. $materialModel->corpid = $corpid;
  611. $materialModel->local_path = $materialInfo->local_path;
  612. $materialModel->type = $type;
  613. $materialModel->oss_url = $materialInfo->oss_url;
  614. $materialModel->media_id = null;
  615. $materialModel->expire_at = date('Y-m-d H:i:s');
  616. $materialModel->save();
  617. $materialId = $materialModel->id;
  618. $materialInfo = $materialModel;
  619. $forceRefresh = true;
  620. }
  621. Log::logInfo('检查下附件过期没', [
  622. 'expire_at' => $materialInfo->expire_at,
  623. 'now' => date('Y-m-d H:i:s')
  624. ], 'MediaIdDeal');
  625. if($materialInfo->expire_at <= date('Y-m-d H:i:s') || $forceRefresh === true) { // 临时素材已过期或要求强制刷新
  626. Log::logInfo('数据库中附件信息过期了', [
  627. 'material_id' => $materialId,
  628. 'corpid' => $corpid,
  629. 'type' => $type,
  630. 'attachment_type' => $attachmentType,
  631. ], 'MediaIdDeal');
  632. $localFilePath = $materialInfo->local_path;
  633. if(!$localFilePath || !file_exists(public_path($localFilePath))) {
  634. $localFilePath = MaterialService::mediaDownload($materialInfo->oss_url, $type, $corpid);
  635. if($localFilePath === false) {
  636. Log::logInfo('数据库中附件本地path异常', [
  637. 'material_id' => $materialId,
  638. 'corpid' => $corpid,
  639. 'type' => $type,
  640. 'attachment_type' => $attachmentType,
  641. ], 'MediaIdDeal');
  642. return false;
  643. }
  644. }
  645. Log::logInfo('数据库中附件本地path', [
  646. 'material_id' => $materialId,
  647. 'corpid' => $corpid,
  648. 'type' => $type,
  649. 'attachment_type' => $attachmentType,
  650. ], 'MediaIdDeal');
  651. if($attachmentType === false) {
  652. $mediaData = MaterialService::uploadMedia($corpid, $localFilePath, $type);
  653. } else {
  654. $mediaData = MaterialService::uploadAttachment($corpid, $localFilePath, $type, $attachmentType);
  655. }
  656. Log::logInfo('新获取到的mediaData', [
  657. 'material_id' => $materialId,
  658. 'corpid' => $corpid,
  659. 'type' => $type,
  660. 'attachment_type' => $attachmentType,
  661. 'mediaData' => $mediaData
  662. ], 'MediaIdDeal');
  663. // @unlink($localFilePath);
  664. if((isset($mediaData['errcode']) && $mediaData['errcode']) || $mediaData === false) {
  665. EmailQueue::rPush('获取素材mediaId失败', json_encode($mediaData, JSON_UNESCAPED_UNICODE), ['xiaohua.hou@kuxuan-inc.com'], '获取素材mediaId失败');
  666. Log::logError('素材ID获取失败', [
  667. 'response' => $mediaData,
  668. 'material_id' => $materialId,
  669. 'corpid' => $corpid,
  670. ], 'MediaIdReplace');
  671. return false;
  672. }
  673. $mediaId = $mediaData['media_id'];
  674. $expireTime = date('Y-m-d H:i:s', $mediaData['created_at'] + 86400 * 3 - 200);
  675. $result = Material::where('id', $materialId)->update([
  676. 'local_path' => $localFilePath,
  677. 'media_id'=>$mediaId,
  678. 'expire_at'=>$expireTime
  679. ]);
  680. if(!$result) {
  681. Log::logError('素材ID更新失败',$materialInfo->toArray(), 'MediaIdReplace');
  682. }
  683. } else {
  684. $mediaId = $materialInfo->media_id;
  685. }
  686. } catch (\Exception $e) {
  687. # 增加报警
  688. EmailQueue::rPush('素材ID获取失败', $e->getTraceAsString(), ['xiaohua.hou@kuxuan-inc.com'], '素材ID获取失败');
  689. Log::logError('素材ID获取失败', [
  690. 'line' => $e->getLine(),
  691. 'msg' => $e->getMessage()
  692. ], 'MediaIdReplace');
  693. return false;
  694. }
  695. return $mediaId;
  696. }
  697. /**
  698. * 下载多媒体资源到本地
  699. * */
  700. public static function mediaDownload($url, $type, $corpid)
  701. {
  702. $url = trim($url, "'");
  703. $url = trim($url, '"');
  704. if(empty($url)) return '';
  705. switch($type) {
  706. case 1:
  707. $fileType = self::getImgType($url);
  708. break;
  709. case 3:
  710. $fileType = 'mp4';
  711. break;
  712. default:
  713. $fileType = 'jpg';
  714. }
  715. if($fileType === false) {
  716. Log::logError('图片后缀获取异常', [
  717. 'url' => $url,
  718. 'type' => $type,
  719. 'corpid' => $corpid
  720. ], 'MediaDownload');
  721. return false;
  722. }
  723. $save_dir = public_path('../storage/uploads/');
  724. // $save_dir = '/mnt/playlet/storage/uploads/';
  725. $fileName = md5($corpid.time().rand(100000,999999)).'.'.$fileType;
  726. try {
  727. //创建保存目录
  728. if(!file_exists($save_dir)) {
  729. if(!mkdir($save_dir, 0777, true)) {
  730. EmailQueue::rPush('下载图片时目录创建失败', $save_dir, ['xiaohua.hou@kuxuan-inc.com'], '下载图片时目录创建失败');
  731. Log::logError('创建目录失败,目录路径:'.$save_dir, [], 'CreateDir');
  732. }
  733. }
  734. //获取远程文件所采用的方法
  735. $ch = curl_init();
  736. $timeout = 20;
  737. curl_setopt($ch, CURLOPT_URL, $url);
  738. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  739. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  740. $content = curl_exec($ch);
  741. curl_close($ch);
  742. $fp2 = @fopen($save_dir . $fileName, 'a');
  743. fwrite($fp2, $content);
  744. fclose($fp2);
  745. unset($content, $url);
  746. } catch (\Exception $e) {
  747. EmailQueue::rPush('素材文件下载抛出异常', $e->getTraceAsString(), ['xiaohua.hou@kuxuan-inc.com'], '素材文件下载抛出异常');
  748. Log::logError('素材文件下载抛出异常', [
  749. 'line' => $e->getLine(),
  750. 'msg' => $e->getMessage(),
  751. 'path' => $save_dir
  752. ], 'CreateDir-Exception');
  753. }
  754. return realpath($save_dir . $fileName);
  755. }
  756. /**
  757. * 获取图片类型
  758. * */
  759. public static function getImgType($url, $retry=0)
  760. {
  761. $suffix = '';
  762. try {
  763. if(empty($suffix) && strstr($url, 'wx_fmt=') !== false) { // 公众号图片
  764. $suffix = self::getUrlParam($url, 'wx_fmt');
  765. }
  766. // Log::logInfo($url);
  767. if(empty($suffix) || $suffix=='other') {
  768. $info = getimagesize($url);
  769. $suffix = false;
  770. if($mime = $info['mime']){
  771. $suffix = explode('/',$mime)[1];
  772. }
  773. }
  774. if(empty($suffix)) {
  775. $suffix = pathinfo($url, PATHINFO_EXTENSION);
  776. }
  777. } catch(\Exception $e) {
  778. Log::logInfo('Error', [
  779. 'url' => $url,
  780. 'msg' => $e->getMessage()
  781. ], 'Error');
  782. $suffix = false;
  783. }
  784. if($suffix === false && $retry <3) { // 发起重试
  785. $retry++;
  786. return self::getImgType($url, $retry);
  787. }
  788. return $suffix ? : false;
  789. }
  790. /**
  791. * 获取url指定参数
  792. * */
  793. public static function getUrlParam($url, $param){
  794. $res = '';
  795. $a = strpos($url,'?');
  796. if($a!==false){
  797. $str = substr($url,$a+1);
  798. $arr = explode('&',$str);
  799. foreach($arr as $k=>$v){
  800. $tmp = explode('=',$v);
  801. if(!empty($tmp[0]) && !empty($tmp[1])){
  802. $barr[$tmp[0]] = $tmp[1];
  803. }
  804. }
  805. }
  806. if(!empty($barr[$param])){
  807. $res = $barr[$param];
  808. }
  809. return $res;
  810. }
  811. /**
  812. * 上传文件到oss
  813. * */
  814. public static function uploadFileToOss($corpid, $type, $file,$isMaterial, &$data)
  815. {
  816. try{
  817. if (empty($file) || !$file->isValid()) {
  818. return 2315;
  819. }
  820. $originalExtension = strtolower($file->getClientOriginalExtension()); //文件扩展名
  821. if(!$originalExtension) $originalExtension = $file->guessExtension();
  822. $realPath = $file->getRealPath(); //临时文件的绝对路径
  823. $mimeType = $file->getClientMimeType(); // image/jpeg
  824. $fileSize = $file->getClientSize();
  825. switch ($type) {
  826. case 1: // 图片
  827. if ($isMaterial && !in_array($originalExtension, ['jpg', 'png'])) {
  828. Log::logError('上传的图片类型为:', ['type' => $originalExtension], 'uploadFileToOss');
  829. return 2301;
  830. }
  831. if($fileSize > 10 * 1024 * 1024) {
  832. return 2306;
  833. }
  834. list($width, $height, $imageType, $attr) = getimagesize($realPath);
  835. if($isMaterial && $width * $height > 1555200) {// 上传素材时验证图片像素,单纯上传图片取消该限制
  836. return 2312;
  837. }
  838. break;
  839. case 2: // 语音
  840. if (!in_array($originalExtension, ['amr'])) {
  841. return 2302;
  842. }
  843. if($fileSize > 2 * 1024 * 1024) {
  844. return 2307;
  845. }
  846. break;
  847. case 3: // 视频
  848. if (!in_array($originalExtension, ['mp4'])) {
  849. return 2303;
  850. }
  851. # 视频文件大小限制
  852. if($fileSize > 20 * 1024 * 1024) {
  853. return 2308;
  854. }
  855. break;
  856. case 4:
  857. if (!in_array($originalExtension, ['pdf'])) {
  858. return 2313;
  859. }
  860. # 文件大小限制
  861. if($fileSize > 20 * 1024 * 1024) {
  862. return 2316;
  863. }
  864. break;
  865. }
  866. // 上传文件
  867. $filename = date('YmdHis') . '-' . uniqid() . '.' . $originalExtension;
  868. // 使用我们新建的uploads本地存储空间(目录)
  869. $result = Storage::disk('uploads')->put($filename, file_get_contents($realPath));
  870. if(!$result)
  871. return 2314;
  872. $oss = new OssService('playlet');
  873. $localFilePath = '../storage/uploads/'.$filename;
  874. $ossFile = $oss->upload($originalExtension, $localFilePath, $mimeType.'/'.date("Y-m-d",time()));
  875. $fileUrl = $ossFile['oss-request-url'];
  876. $data = array_filter([
  877. 'url' => $fileUrl,
  878. 'local_path' => $localFilePath
  879. ]);
  880. } catch (\Exception $e) {
  881. EmailQueue::rPush('文件上传过程发生异常', $e->getTraceAsString(), [
  882. 'xiaohua.hou@kuxuan-inc.com'], '文件上传过程发生异常');
  883. Log::logError('文件上传过程发生异常', [
  884. 'line' => $e->getLine(),
  885. 'msg' => $e->getMessage()
  886. ], 'UploadMaterial-Exception');
  887. return 2317;
  888. }
  889. return 0;
  890. }
  891. public static function itemUrlReplace($attachments, $itemUrl)
  892. {
  893. if(empty($attachments)) return [];
  894. if(empty($itemUrl)) return $attachments;
  895. foreach ($attachments as $key=>&$attachment) {
  896. if(isset($attachment['link']['url'])) {
  897. $attachment['link']['url'] = $itemUrl;
  898. }
  899. if(isset($attachment['link']['jump_url'])) {
  900. $attachment['link']['jump_url'] = $itemUrl;
  901. }
  902. if(isset($attachment['promote']['jump_url'])) {
  903. $attachment['promote']['jump_url'] = $itemUrl;
  904. }
  905. if(isset($attachment['promote']['url'])) {
  906. $attachment['promote']['url'] = $itemUrl;
  907. }
  908. }
  909. return $attachments;
  910. }
  911. public static function turnToH5($attachments)
  912. {
  913. if(empty($attachments)) return [];
  914. foreach ($attachments as $key=>&$attachment) {
  915. # 图片类型media_id转换为mediaid
  916. if(isset($attachment['image']['media_id'])) {
  917. $attachment['image']['mediaid'] = $attachment['image']['media_id'];
  918. }
  919. # 图片类型 pic_url转换为 imgUrl
  920. if(isset($attachment['image']['pic_url'])) {
  921. $attachment['image']['imgUrl'] = $attachment['image']['pic_url'];
  922. }
  923. # 链接类型 picurl 转换为 imgUrl
  924. if(isset($attachment['link']['picurl'])) {
  925. $attachment['link']['imgUrl'] = $attachment['link']['picurl'];
  926. }
  927. # video类型media_id转换为mediaid
  928. if(isset($attachment['video']['media_id'])) {
  929. $attachment['video']['mediaid'] = $attachment['video']['media_id'];
  930. }
  931. # file类型media_id转换为mediaid
  932. if(isset($attachment['file']['media_id'])) {
  933. $attachment['file']['mediaid'] = $attachment['file']['media_id'];
  934. }
  935. # 小程序封面图替换
  936. if(isset($attachment['miniprogram']['pic_url'])){
  937. $attachment['miniprogram']['imgUrl']= $attachment['miniprogram']['pic_url'];
  938. }
  939. }
  940. return $attachments;
  941. }
  942. /**
  943. * 上传图片到企微
  944. * */
  945. public static function uploadImg($corpid, $filePath, $retry=0)
  946. {
  947. $accessToken = AuthorizeCorp::getAccessToken($corpid, '上传图片到企微');
  948. if(empty($accessToken)) {
  949. return false;
  950. }
  951. $requestUri = config('qyWechat.upload_img');
  952. $requestUri .= $accessToken;
  953. $response = HttpService::httpUpload($requestUri, $filePath);
  954. $responseData = json_decode($response, true);
  955. # 发起重试
  956. if($response===false || $responseData['errcode'] == -1) { // 企微系统繁忙,发起重试
  957. if($retry < 5) {
  958. Log::logInfo('企微系统繁忙,发起重试', [
  959. 'retry' => $retry,
  960. 'response' => $responseData
  961. ], 'UploadImg');
  962. $retry++;
  963. return MaterialService::uploadImg($filePath, $retry);
  964. }
  965. }
  966. Log::logInfo('上传图片到企微返回结果', [
  967. 'retry' => $retry,
  968. 'corpid' => $corpid,
  969. 'response' => $responseData
  970. ], 'UploadImg');
  971. return $responseData['url'] ?? '';
  972. }
  973. /**
  974. * 通过图片链接上传资源到oss
  975. * */
  976. public static function uploadFileByUrl($corpid, $imgUrl, &$ossUrl)
  977. {
  978. try{
  979. # 下载图片
  980. $realPath = MaterialService::mediaDownload($imgUrl, 1, $corpid);
  981. if($realPath === false) return 2319;
  982. # 上传图片
  983. $originalExtension = MaterialService::getImgType($imgUrl); //文件扩展名
  984. if($originalExtension === false) return 2319;
  985. $oss = new OssService('playlet');
  986. $ossFile = $oss->upload($originalExtension, $realPath, 'image/'.date("Y-m-d",time()));
  987. # 上传成功删除本地图片
  988. @unlink($realPath);
  989. # 返回oss链接
  990. $ossUrl = $ossFile['oss-request-url'];
  991. } catch (\Exception $e) {
  992. EmailQueue::rPush('通过图片链接上传资源到oss流程出现异常', $e->getTraceAsString(), ['xiaohua.hou@kuxuan-inc.com'], '创建朋友圈消息流程出现异常');
  993. Log::logError('通过图片链接上传资源到oss流程出现异常', [
  994. 'line' => $e->getLine(),
  995. 'msg' => $e->getMessage(),
  996. 'corpid' => $corpid,
  997. 'imgUrl' => $imgUrl
  998. ], 'uploadFileByUrl');
  999. return 2319;
  1000. }
  1001. return 0;
  1002. }
  1003. }