MessageService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. namespace app\common\library\xmwechat\offiaccount;
  3. use app\admin\model\xmwechat\offiaccount\Autoreply;
  4. use app\admin\model\xmwechat\offiaccount\Material;
  5. use app\common\library\xmwechat\SingletonTrait;
  6. use app\common\library\xmwechat\WechatService;
  7. use EasyWeChat\Kernel\Exceptions\BadRequestException;
  8. use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
  9. use EasyWeChat\Kernel\Exceptions\RuntimeException;
  10. use EasyWeChat\OfficialAccount\Application;
  11. use Psr\Http\Message\ResponseInterface;
  12. use think\db\exception\DataNotFoundException;
  13. use think\db\exception\DbException;
  14. use think\db\exception\ModelNotFoundException;
  15. use think\exception\HttpResponseException;
  16. use think\facade\Log;
  17. use think\Response;
  18. use think\facade\Request;
  19. use think\facade\Db;
  20. /**
  21. * 消息推送
  22. */
  23. class MessageService extends WechatService
  24. {
  25. protected ?Application $app;
  26. protected string $accessToken;
  27. use SingletonTrait;
  28. protected HttpClientInterface $httpClient;
  29. public function __construct()
  30. {
  31. parent::__construct();
  32. if (empty($this->offiAccountConfig['app_id']) || empty($this->offiAccountConfig['secret'])) {
  33. $this->result('请配置公众号appid或secret');
  34. }
  35. $this->app = new Application([
  36. 'app_id' => $this->offiAccountConfig['app_id'],
  37. 'secret' => $this->offiAccountConfig['secret'],
  38. 'token' => $this->offiAccountConfig['server_token'],
  39. 'aes_key' => $this->offiAccountConfig['encoding_aes_key'],
  40. ]);
  41. $accessToken = $this->app->getAccessToken();
  42. $this->accessToken = $accessToken->getToken();
  43. // file_put_contents(dirname(__FILE__) . '/Token.txt',$this->accessToken);
  44. }
  45. /**
  46. * 微信开发文档 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
  47. * @return ResponseInterface
  48. * @throws InvalidArgumentException
  49. * @throws BadRequestException
  50. * @throws RuntimeException
  51. * @throws \ReflectionException
  52. * @throws \Throwable
  53. * @author jsjxsz
  54. */
  55. public function messageServe(): ResponseInterface
  56. {
  57. $server = $this->app->getServer();
  58. // 自定义菜单click事件
  59. // {"ToUserName":"gh_3fd79f90b5af","FromUserName":"oluKa548P2NdCeiEbvlAomQxLKfM","CreateTime":"1693377845","MsgType":"event","Event":"CLICK","EventKey":"fq99mlqge3"}
  60. $server->addEventListener('CLICK', function ($message) {
  61. Log::info('event-message-CLICK' . json_encode($message,JSON_UNESCAPED_UNICODE));
  62. $eventKey = $message['EventKey'];
  63. return $this->getReplyContentByEvent('click', $eventKey);
  64. });
  65. // 订阅事件
  66. // {"ToUserName":"gh_3fd79f90b5af","FromUserName":"oluKa548P2NdCeiEbvlAomQxLKfM","CreateTime":"1693377844","MsgType":"event","Event":"subscribe","EventKey":""}
  67. $server->addEventListener('subscribe', function ($message) {
  68. // 发起 GET 请求
  69. $url='https://api.weixin.qq.com/cgi-bin/user/info?access_token='.$this->accessToken.'&openid='.$message['FromUserName'].'&lang=zh_CN';
  70. $options = [
  71. 'http' => [
  72. 'method' => 'GET',
  73. 'header' => 'Content-type: application/x-www-form-urlencoded\r\n',
  74. ],
  75. ];
  76. $context = stream_context_create($options);
  77. $response = file_get_contents($url, false, $context);
  78. $response=json_decode($response, true);
  79. Db::name('oauth_log')->where('uuid', $response['unionid'])->update(['opid' => $response['openid'],'opid_status' => '1']);
  80. Log::info('event-message-subscribe' . json_encode($message,JSON_UNESCAPED_UNICODE));
  81. return $this->getReplyContentByEvent('subscribe');
  82. });
  83. //解除绑定
  84. $server->addEventListener('unsubscribe', function ($message) {
  85. Db::name('oauth_log')->where('opid', $message['FromUserName'])->update(['opid' => null,'opid_status' => null]);
  86. // Log::info('接收普通消息text-message' . json_encode($message,JSON_UNESCAPED_UNICODE));
  87. // return $this->getReplyContentByMessage($message['Content']);
  88. });
  89. // 接收普通消息
  90. $server->addMessageListener('text', function ($message) {
  91. Log::info('接收普通消息text-message' . json_encode($message,JSON_UNESCAPED_UNICODE));
  92. return $this->getReplyContentByMessage($message['Content']);
  93. });
  94. return $server->serve();
  95. }
  96. /**
  97. * 事件消息回复内容
  98. * @param $event
  99. * @return array|string
  100. * @throws DataNotFoundException
  101. * @throws DbException
  102. * @throws ModelNotFoundException
  103. * @author jsjxsz
  104. */
  105. public function getReplyContentByEvent($event, $eventKey = ''): array|string
  106. {
  107. if ($event == 'subscribe') {
  108. // 订阅事件回复内容
  109. $autoreply = Autoreply::where('type', 'follow')->where('status', 1)->find();
  110. if ($autoreply['msg_type'] == 'text') {
  111. return htmlspecialchars_decode($autoreply['reply_content']);
  112. } elseif ($autoreply['msg_type'] == 'image') {
  113. $content = json_decode($autoreply['reply_content'], true);
  114. return [
  115. 'MsgType' => 'image',
  116. 'Image' => [
  117. 'MediaId' => $content['media_id'],
  118. ],
  119. ];
  120. } elseif ($autoreply['msg_type'] == 'voice') {
  121. $content = json_decode($autoreply['reply_content'], true);
  122. return [
  123. 'MsgType' => 'voice',
  124. 'Voice' => [
  125. 'MediaId' => $content['media_id'],
  126. ],
  127. ];
  128. } elseif ($autoreply['msg_type'] == 'video') {
  129. $content = json_decode($autoreply['reply_content'], true);
  130. return [
  131. 'MsgType' => 'video',
  132. 'Video' => [
  133. 'MediaId' => $content['media_id'],
  134. 'Title' => $content['name'],
  135. 'Description' => $content['description'],
  136. ],
  137. ];
  138. } elseif ($autoreply['msg_type'] == 'news') {
  139. $content = json_decode($autoreply['reply_content'], true);
  140. return [
  141. 'MsgType' => 'news',
  142. 'Articles' => [
  143. 'item' => [
  144. 'Title' => $content['title'],
  145. 'Description' => $content['digest'],
  146. 'PicUrl' => $content['show_cover_pic'],
  147. 'Url' => $content['url'],
  148. ]
  149. ],
  150. ];
  151. } else {
  152. return $this->getDefaultReply();
  153. }
  154. } else if ($event == 'click') {
  155. $content = Material::where('event_key', $eventKey)->value('content');
  156. return htmlspecialchars_decode($content);
  157. } else {
  158. return $this->getDefaultReply();
  159. }
  160. }
  161. // 普通消息获取回复内容
  162. public function getReplyContentByMessage($content)
  163. {
  164. $autoreply = Autoreply::where('keyword', $content)->where('status', 1)->find();
  165. if (!empty($autoreply)) {
  166. $msg_type = $autoreply['msg_type'];
  167. if ($msg_type == 'text') {
  168. return htmlspecialchars_decode($autoreply['reply_content']);
  169. } elseif ($msg_type == 'image') {
  170. $content = json_decode($autoreply['reply_content'], true);
  171. return [
  172. 'MsgType' => 'image',
  173. 'Image' => [
  174. 'MediaId' => $content['media_id'],
  175. ],
  176. ];
  177. } elseif ($msg_type == 'voice') {
  178. $content = json_decode($autoreply['reply_content'], true);
  179. return [
  180. 'MsgType' => 'voice',
  181. 'Voice' => [
  182. 'MediaId' => $content['media_id'],
  183. ],
  184. ];
  185. } elseif ($msg_type == 'video') {
  186. $content = json_decode($autoreply['reply_content'], true);
  187. return [
  188. 'MsgType' => 'video',
  189. 'Video' => [
  190. 'MediaId' => $content['media_id'],
  191. 'Title' => $content['name'],
  192. 'Description' => $content['description'],
  193. ],
  194. ];
  195. } elseif ($msg_type == 'news') {
  196. $content = json_decode($autoreply['reply_content'], true);
  197. return [
  198. 'MsgType' => 'news',
  199. 'Articles' => [
  200. 'item' => [
  201. 'Title' => $content['title'],
  202. 'Description' => $content['digest'],
  203. 'PicUrl' => $content['show_cover_pic'],
  204. 'Url' => $content['url'],
  205. ]
  206. ],
  207. ];
  208. } else {
  209. return $this->getDefaultReply();
  210. }
  211. } else {
  212. return $this->getDefaultReply();
  213. }
  214. }
  215. /**
  216. * 默认回复
  217. * @return mixed
  218. * @author jsjxsz
  219. */
  220. public function getDefaultReply(): mixed
  221. {
  222. $content = Autoreply::where('type', 'default')->where('status', 1)->value('reply_content');
  223. return htmlspecialchars_decode($content);
  224. }
  225. /**
  226. * @param array $params
  227. * @return bool
  228. * @author jsjxsz
  229. */
  230. public function checkSignature(array $params): bool
  231. {
  232. $offiAccount = config('xmwechat')['offiAccount'];
  233. $server_token = $offiAccount['server_token'];
  234. $tmpArr = array($server_token, $params['timestamp'], $params['nonce']);
  235. sort($tmpArr, SORT_STRING);
  236. $tmpStr = implode($tmpArr);
  237. $tmpStr = sha1($tmpStr);
  238. if ($tmpStr == $params['signature']) {
  239. return true;
  240. } else {
  241. return false;
  242. }
  243. }
  244. }