BasicPushEvent.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | WeChatDeveloper
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2014~2023 ThinkAdmin [ thinkadmin.top ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: https://thinkadmin.top
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( https://mit-license.org )
  10. // | 免责声明 ( https://thinkadmin.top/disclaimer )
  11. // +----------------------------------------------------------------------
  12. // | gitee 代码仓库:https://gitee.com/zoujingli/WeChatDeveloper
  13. // | github 代码仓库:https://github.com/zoujingli/WeChatDeveloper
  14. // +----------------------------------------------------------------------
  15. namespace WeChat\Contracts;
  16. use WeChat\Exceptions\InvalidArgumentException;
  17. use WeChat\Exceptions\InvalidDecryptException;
  18. use WeChat\Exceptions\InvalidResponseException;
  19. use WeChat\Prpcrypt\Prpcrypt;
  20. /**
  21. * 微信通知处理基本类
  22. * Class BasicPushEvent
  23. * @package WeChat\Contracts
  24. */
  25. class BasicPushEvent
  26. {
  27. /**
  28. * 公众号APPID
  29. * @var string
  30. */
  31. protected $appid;
  32. /**
  33. * 公众号推送XML内容
  34. * @var string
  35. */
  36. protected $postxml;
  37. /**
  38. * 公众号推送加密类型
  39. * @var string
  40. */
  41. protected $encryptType;
  42. /**
  43. * 公众号的推送请求参数
  44. * @var DataArray
  45. */
  46. protected $input;
  47. /**
  48. * 当前公众号配置对象
  49. * @var DataArray
  50. */
  51. protected $config;
  52. /**
  53. * 公众号推送内容对象
  54. * @var DataArray
  55. */
  56. protected $receive;
  57. /**
  58. * 准备回复的消息内容
  59. * @var array
  60. */
  61. protected $message;
  62. /**
  63. * BasicPushEvent constructor.
  64. * @param array $options
  65. * @throws \WeChat\Exceptions\InvalidResponseException
  66. */
  67. public function __construct(array $options)
  68. {
  69. if (empty($options['appid'])) {
  70. throw new InvalidArgumentException("Missing Config -- [appid]");
  71. }
  72. if (empty($options['appsecret'])) {
  73. throw new InvalidArgumentException("Missing Config -- [appsecret]");
  74. }
  75. if (empty($options['token'])) {
  76. throw new InvalidArgumentException("Missing Config -- [token]");
  77. }
  78. // 参数初始化
  79. $this->config = new DataArray($options);
  80. $this->input = new DataArray($_REQUEST);
  81. $this->appid = $this->config->get('appid');
  82. // 推送消息处理
  83. if ($_SERVER['REQUEST_METHOD'] == "POST") {
  84. $this->postxml = Tools::getRawInput();
  85. $this->encryptType = $this->input->get('encrypt_type');
  86. if ($this->isEncrypt()) {
  87. if (empty($options['encodingaeskey'])) {
  88. throw new InvalidArgumentException("Missing Config -- [encodingaeskey]");
  89. }
  90. $prpcrypt = new Prpcrypt($this->config->get('encodingaeskey'));
  91. $result = Tools::xml2arr($this->postxml);
  92. $array = $prpcrypt->decrypt($result['Encrypt']);
  93. if (intval($array[0]) > 0) {
  94. throw new InvalidResponseException($array[1], $array[0]);
  95. }
  96. list($this->postxml, $this->appid) = [$array[1], $array[2]];
  97. }
  98. $this->receive = new DataArray(Tools::xml2arr($this->postxml));
  99. } elseif ($_SERVER['REQUEST_METHOD'] == "GET" && $this->checkSignature()) {
  100. @ob_clean();
  101. exit($this->input->get('echostr'));
  102. } else {
  103. throw new InvalidResponseException('Invalid interface request.', '0');
  104. }
  105. }
  106. /**
  107. * 消息是否需要加密
  108. * @return boolean
  109. */
  110. public function isEncrypt()
  111. {
  112. return $this->encryptType === 'aes';
  113. }
  114. /**
  115. * 回复消息
  116. * @param array $data 消息内容
  117. * @param boolean $return 是否返回XML内容
  118. * @param boolean $isEncrypt 是否加密内容
  119. * @return string
  120. * @throws \WeChat\Exceptions\InvalidDecryptException
  121. */
  122. public function reply(array $data = [], $return = false, $isEncrypt = false)
  123. {
  124. $xml = Tools::arr2xml(empty($data) ? $this->message : $data);
  125. if ($this->isEncrypt() || $isEncrypt) {
  126. $prpcrypt = new Prpcrypt($this->config->get('encodingaeskey'));
  127. // 如果是第三方平台,加密得使用 component_appid
  128. $component_appid = $this->config->get('component_appid');
  129. $appid = empty($component_appid) ? $this->appid : $component_appid;
  130. $array = $prpcrypt->encrypt($xml, $appid);
  131. if ($array[0] > 0) throw new InvalidDecryptException('Encrypt Error.', '0');
  132. list($timestamp, $encrypt) = [time(), $array[1]];
  133. $nonce = rand(77, 999) * rand(605, 888) * rand(11, 99);
  134. $tmpArr = [$this->config->get('token'), $timestamp, $nonce, $encrypt];
  135. sort($tmpArr, SORT_STRING);
  136. $signature = sha1(implode($tmpArr));
  137. $format = "<xml><Encrypt><![CDATA[%s]]></Encrypt><MsgSignature><![CDATA[%s]]></MsgSignature><TimeStamp>%s</TimeStamp><Nonce><![CDATA[%s]]></Nonce></xml>";
  138. $xml = sprintf($format, $encrypt, $signature, $timestamp, $nonce);
  139. }
  140. if ($return) return $xml;
  141. @ob_clean();
  142. echo $xml;
  143. }
  144. /**
  145. * 验证来自微信服务器
  146. * @return bool
  147. */
  148. private function checkSignature()
  149. {
  150. $nonce = $this->input->get('nonce');
  151. $timestamp = $this->input->get('timestamp');
  152. $msg_signature = $this->input->get('msg_signature');
  153. $signature = empty($msg_signature) ? $this->input->get('signature') : $msg_signature;
  154. $tmpArr = [$this->config->get('token'), $timestamp, $nonce, ''];
  155. sort($tmpArr, SORT_STRING);
  156. return sha1(implode($tmpArr)) === $signature;
  157. }
  158. /**
  159. * 获取公众号推送对象
  160. * @param null|string $field 指定获取字段
  161. * @return array
  162. */
  163. public function getReceive($field = null)
  164. {
  165. return $this->receive->get($field);
  166. }
  167. /**
  168. * 获取当前微信OPENID
  169. * @return string
  170. */
  171. public function getOpenid()
  172. {
  173. return $this->receive->get('FromUserName');
  174. }
  175. /**
  176. * 获取当前推送消息类型
  177. * @return string
  178. */
  179. public function getMsgType()
  180. {
  181. return $this->receive->get('MsgType');
  182. }
  183. /**
  184. * 获取当前推送消息ID
  185. * @return string
  186. */
  187. public function getMsgId()
  188. {
  189. return $this->receive->get('MsgId');
  190. }
  191. /**
  192. * 获取当前推送时间
  193. * @return integer
  194. */
  195. public function getMsgTime()
  196. {
  197. return $this->receive->get('CreateTime');
  198. }
  199. /**
  200. * 获取当前推送公众号
  201. * @return string
  202. */
  203. public function getToOpenid()
  204. {
  205. return $this->receive->get('ToUserName');
  206. }
  207. }