GZHSendKFMessage.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace App\Jobs\WechatPlatform;
  3. use App\Service\Util\Support\Http\HttpRequestService;
  4. use App\Service\Util\Support\Trace\TraceContext;
  5. use App\Service\WechatPlatform\GZHSendKFMessageService;
  6. use App\Service\WechatPlatform\WechatPlatform;
  7. use EasyWeChat\OfficialAccount\Application;
  8. use Illuminate\Bus\Queueable;
  9. use Illuminate\Contracts\Queue\ShouldQueue;
  10. use Illuminate\Foundation\Bus\Dispatchable;
  11. use Illuminate\Queue\InteractsWithQueue;
  12. use Illuminate\Queue\SerializesModels;
  13. use Illuminate\Support\Facades\DB;
  14. class GZHSendKFMessage implements ShouldQueue
  15. {
  16. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  17. /**
  18. * @var
  19. * <pre>
  20. * [
  21. * 'gzhId' => $gzhId, // wechat_authorization_infos.id
  22. * 'messageId' => $item->id, // wechat_kf_messages.id
  23. * 'traceInfo' => $traceContext->getTraceInfo() // traceInfo
  24. * ]
  25. * </pre>
  26. */
  27. private $info;
  28. /**
  29. * @var TraceContext
  30. */
  31. private $traceContext;
  32. /**
  33. * Create a new job instance.
  34. */
  35. public function __construct($info)
  36. {
  37. $this->info = $info;
  38. }
  39. /**
  40. * Execute the job.
  41. */
  42. public function handle(): void
  43. {
  44. $this->traceContext = TraceContext::newFromParent($this->info['traceInfo']);
  45. myLog('KFMessageSend')->info('公众号开始发送客服消息', [
  46. 'info' => $this->info,
  47. 'traceInfo' => $this->traceContext->getTraceInfo(),
  48. ]);
  49. $gzh = $this->getGZH();
  50. if (!$gzh) return;
  51. $message = $this->getMessage();
  52. if (!$message) return;
  53. $messageContent = collect(\json_decode($message->message_content, true));
  54. $messageStr = $messageContent->pluck('text')->join("\n\n");
  55. $officialAccount = $this->getOfficialAccount($gzh);
  56. if (false === $officialAccount) return;
  57. if ($this->info['isTest'] ?? false) {
  58. $openid = $this->info['openid'] ?? '';
  59. if (!$openid) {
  60. myLog('KFMessageSend')->error('测试回传没有openid', [
  61. 'info' => $this->info
  62. ]);
  63. }
  64. GZHSendKFMessageService::sendText($officialAccount, $openid, $messageStr, $this->traceContext);
  65. } else {
  66. $next_openid = '';
  67. $loop = 1;
  68. while (true) {
  69. if ($loop++ > 10000) {
  70. break;
  71. }
  72. if (1 == $message->u_type) {
  73. $info = $this->getUserOpenids($officialAccount, $next_openid);
  74. foreach (($info['data']['openid'] ?? []) as $opid) {
  75. GZHSendKFMessageService::sendText($officialAccount, $opid, $messageStr, $this->traceContext);
  76. }
  77. $next_openid = $info['next_openid'] ?? '';
  78. if (!$next_openid) {
  79. break;
  80. }
  81. } elseif (2 == $message->u_type) {
  82. $info = $this->getUsersFromUG($gzh->id, $message->ug_id, $next_openid);
  83. foreach (($info['openid'] ?? []) as $opid) {
  84. GZHSendKFMessageService::sendText($officialAccount, $opid, $messageStr, $this->traceContext);
  85. }
  86. $next_openid = $info['next_uid'] ?? '';
  87. if (!$next_openid) {
  88. break;
  89. }
  90. }
  91. }
  92. }
  93. }
  94. private function getMessage()
  95. {
  96. $message = DB::table('wechat_kf_messages')
  97. ->where('id', $this->info['messageId'])
  98. ->first();
  99. if (!$message) {
  100. myLog('KFMessageSend')->error('消息不存在', [
  101. 'info' => $this->info,
  102. 'traceInfo' => $this->traceContext->getTraceInfo(),
  103. ]);
  104. return false;
  105. }
  106. if (1 != $message->message_type) {
  107. myLog('KFMessageSend')->error('不支持的消息类型', [
  108. 'info' => $this->info,
  109. 'traceInfo' => $this->traceContext->getTraceInfo(),
  110. ]);
  111. return false;
  112. }
  113. return $message;
  114. }
  115. /**
  116. * 拉取公众号粉丝
  117. * @param $officialAccount Application
  118. */
  119. private function getUserOpenids($officialAccount, $next_openid)
  120. {
  121. $result = $officialAccount->user->list($next_openid);
  122. if (0 != ($result['errcode'] ?? 0)) {
  123. return false;
  124. }
  125. return $result;
  126. }
  127. /**
  128. * 通过用户分群获取
  129. * @param $gzhId
  130. * @param $ugId
  131. * @param $nextUid
  132. * @return false|mixed
  133. */
  134. private function getUsersFromUG($gzhId, $ugId, $nextUid)
  135. {
  136. $url = config('wechat.ug.url.listUser');
  137. $signKey = config('wechat.ug.signKey');
  138. $now = time();
  139. $res = HttpRequestService::simpleGet($url, [
  140. 'timestamp' => $now,
  141. 'sign' => md5($signKey . $now),
  142. 'gzhId' => $gzhId,
  143. 'ugId' => $ugId,
  144. 'nextUid' => $nextUid,
  145. 'limit' => 1000
  146. ]);
  147. if ($res) {
  148. myLog('KFMessageSend')->debug('通过用户分群获取用户列表', [
  149. 'gzhId' => $gzhId,
  150. 'ugId' => $ugId,
  151. 'res' => $res
  152. ]);
  153. if (10000 == ($res['code'] ?? 10000)) {
  154. return $res;
  155. }
  156. }
  157. return false;
  158. }
  159. /**
  160. * 获取公众号调用对象
  161. * @param $gzh
  162. * @return \EasyWeChat\OfficialAccount\Application
  163. * @throws \EasyWeChat\Kernel\Exceptions\BadResponseException
  164. * @throws \EasyWeChat\Kernel\Exceptions\HttpException
  165. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  166. * @throws \Psr\SimpleCache\InvalidArgumentException
  167. * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  168. * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
  169. * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  170. * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  171. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  172. */
  173. private function getOfficialAccount($gzh)
  174. {
  175. try {
  176. return WechatPlatform::buildApplication($gzh)
  177. ->officialAccount($gzh->authorizer_appid, $gzh->authorizer_refresh_token);
  178. } catch (\Throwable $exception) {
  179. myLog('KFMessageSend')->error('获取公众号调用对象失败', [
  180. 'exceptionMessage' => $exception->getMessage(),
  181. 'traceInfo' => $this->traceContext->getTraceInfo()
  182. ]);
  183. return false;
  184. }
  185. }
  186. private function getGZH()
  187. {
  188. $gzh = DB::table('wechat_authorization_infos as a')
  189. ->join('wechat_open_platform_infos as b', 'a.component_appid', 'b.app_id')
  190. ->where([
  191. ['a.id', '=', $this->info['gzhId']],
  192. ['a.is_enabled', '=', 1],
  193. ['b.is_enabled', '=', 1]
  194. ])->select('a.id', 'a.authorizer_appid', 'a.authorizer_refresh_token',
  195. 'b.app_id', 'b.secret', 'b.token', 'b.aes_key')
  196. ->first();
  197. if (!$gzh) {
  198. myLog('KFMessageSend')->error('公众号不可用', [
  199. 'traceInfo' => $this->traceContext->getTraceInfo(),
  200. ]);
  201. }
  202. return $gzh;
  203. }
  204. }