BasicWeChat.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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\InvalidResponseException;
  18. /**
  19. * Class BasicWeChat
  20. * @package WeChat\Contracts
  21. */
  22. class BasicWeChat
  23. {
  24. /**
  25. * 当前微信配置
  26. * @var DataArray
  27. */
  28. public $config;
  29. /**
  30. * 访问AccessToken
  31. * @var string
  32. */
  33. public $access_token = '';
  34. /**
  35. * 当前请求方法参数
  36. * @var array
  37. */
  38. protected $currentMethod = [];
  39. /**
  40. * 当前模式
  41. * @var bool
  42. */
  43. protected $isTry = false;
  44. /**
  45. * 静态缓存
  46. * @var static
  47. */
  48. protected static $cache;
  49. /**
  50. * 注册代替函数
  51. * @var string
  52. */
  53. protected $GetAccessTokenCallback;
  54. /**
  55. * BasicWeChat constructor.
  56. * @param array $options
  57. */
  58. public function __construct(array $options)
  59. {
  60. if (empty($options['appid'])) {
  61. throw new InvalidArgumentException("Missing Config -- [appid]");
  62. }
  63. if (empty($options['appsecret'])) {
  64. throw new InvalidArgumentException("Missing Config -- [appsecret]");
  65. }
  66. if (isset($options['GetAccessTokenCallback']) && is_callable($options['GetAccessTokenCallback'])) {
  67. $this->GetAccessTokenCallback = $options['GetAccessTokenCallback'];
  68. }
  69. if (!empty($options['cache_path'])) {
  70. Tools::$cache_path = $options['cache_path'];
  71. }
  72. $this->config = new DataArray($options);
  73. }
  74. /**
  75. * 静态创建对象
  76. * @param array $config
  77. * @return static
  78. */
  79. public static function instance(array $config)
  80. {
  81. $key = md5(get_called_class() . serialize($config));
  82. if (isset(self::$cache[$key])) return self::$cache[$key];
  83. return self::$cache[$key] = new static($config);
  84. }
  85. /**
  86. * 获取访问 AccessToken
  87. * @return string
  88. * @throws \WeChat\Exceptions\InvalidResponseException
  89. * @throws \WeChat\Exceptions\LocalCacheException
  90. */
  91. public function getAccessToken()
  92. {
  93. if (!empty($this->access_token)) {
  94. return $this->access_token;
  95. }
  96. $cache = $this->config->get('appid') . '_access_token';
  97. $this->access_token = Tools::getCache($cache);
  98. if (!empty($this->access_token)) {
  99. return $this->access_token;
  100. }
  101. // 处理开放平台授权公众号获取AccessToken
  102. if (!empty($this->GetAccessTokenCallback) && is_callable($this->GetAccessTokenCallback)) {
  103. $this->access_token = call_user_func_array($this->GetAccessTokenCallback, [$this->config->get('appid'), $this]);
  104. if (!empty($this->access_token)) {
  105. Tools::setCache($cache, $this->access_token, 7000);
  106. }
  107. return $this->access_token;
  108. }
  109. list($appid, $secret) = [$this->config->get('appid'), $this->config->get('appsecret')];
  110. $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
  111. $result = Tools::json2arr(Tools::get($url));
  112. if (!empty($result['access_token'])) {
  113. Tools::setCache($cache, $result['access_token'], 7000);
  114. }
  115. return $this->access_token = $result['access_token'];
  116. }
  117. /**
  118. * 设置外部接口 AccessToken
  119. * @param string $accessToken
  120. * @throws \WeChat\Exceptions\LocalCacheException
  121. * @author 高一平 <iam@gaoyiping.com>
  122. *
  123. * 当用户使用自己的缓存驱动时,直接实例化对象后可直接设置 AccessToken
  124. * - 多用于分布式项目时保持 AccessToken 统一
  125. * - 使用此方法后就由用户来保证传入的 AccessToken 为有效 AccessToken
  126. */
  127. public function setAccessToken($accessToken)
  128. {
  129. if (!is_string($accessToken)) {
  130. throw new InvalidArgumentException("Invalid AccessToken type, need string.");
  131. }
  132. $cache = $this->config->get('appid') . '_access_token';
  133. Tools::setCache($cache, $this->access_token = $accessToken);
  134. }
  135. /**
  136. * 清理删除 AccessToken
  137. * @return bool
  138. */
  139. public function delAccessToken()
  140. {
  141. $this->access_token = '';
  142. return Tools::delCache($this->config->get('appid') . '_access_token');
  143. }
  144. /**
  145. * 以GET获取接口数据并转为数组
  146. * @param string $url 接口地址
  147. * @return array
  148. * @throws \WeChat\Exceptions\InvalidResponseException
  149. * @throws \WeChat\Exceptions\LocalCacheException
  150. */
  151. protected function httpGetForJson($url)
  152. {
  153. try {
  154. return Tools::json2arr(Tools::get($url));
  155. } catch (InvalidResponseException $exception) {
  156. if (isset($this->currentMethod['method']) && empty($this->isTry)) {
  157. if (in_array($exception->getCode(), ['40014', '40001', '41001', '42001'])) {
  158. [$this->delAccessToken(), $this->isTry = true];
  159. return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
  160. }
  161. }
  162. throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
  163. }
  164. }
  165. /**
  166. * 以POST获取接口数据并转为数组
  167. * @param string $url 接口地址
  168. * @param array $data 请求数据
  169. * @param bool $buildToJson
  170. * @return array
  171. * @throws \WeChat\Exceptions\InvalidResponseException
  172. * @throws \WeChat\Exceptions\LocalCacheException
  173. */
  174. protected function httpPostForJson($url, array $data, $buildToJson = true)
  175. {
  176. try {
  177. $options = [];
  178. if ($buildToJson) $options['headers'] = ['Content-Type: application/json'];
  179. return Tools::json2arr(Tools::post($url, $buildToJson ? Tools::arr2json($data) : $data, $options));
  180. } catch (InvalidResponseException $exception) {
  181. if (!$this->isTry && in_array($exception->getCode(), ['40014', '40001', '41001', '42001','43004'])) {
  182. [$this->delAccessToken(), $this->isTry = true];
  183. return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
  184. }
  185. return ['errcode' => $exception->getCode(), 'errmsg' => $exception->getMessage()];
  186. //throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
  187. }
  188. }
  189. /**
  190. * 注册当前请求接口
  191. * @param string $url 接口地址
  192. * @param string $method 当前接口方法
  193. * @param array $arguments 请求参数
  194. * @return string
  195. * @throws \WeChat\Exceptions\InvalidResponseException
  196. * @throws \WeChat\Exceptions\LocalCacheException
  197. */
  198. protected function registerApi(&$url, $method, $arguments = [])
  199. {
  200. $this->currentMethod = ['method' => $method, 'arguments' => $arguments];
  201. if (empty($this->access_token)) $this->access_token = $this->getAccessToken();
  202. return $url = str_replace('ACCESS_TOKEN', urlencode($this->access_token), $url);
  203. }
  204. /**
  205. * 接口通用POST请求方法
  206. * @param string $url 接口URL
  207. * @param array $data POST提交接口参数
  208. * @param bool $isBuildJson
  209. * @return array
  210. * @throws \WeChat\Exceptions\InvalidResponseException
  211. * @throws \WeChat\Exceptions\LocalCacheException
  212. */
  213. public function callPostApi($url, array $data, $isBuildJson = true)
  214. {
  215. $this->registerApi($url, __FUNCTION__, func_get_args());
  216. return $this->httpPostForJson($url, $data, $isBuildJson);
  217. }
  218. /**
  219. * 接口通用GET请求方法
  220. * @param string $url 接口URL
  221. * @return array
  222. * @throws \WeChat\Exceptions\InvalidResponseException
  223. * @throws \WeChat\Exceptions\LocalCacheException
  224. */
  225. public function callGetApi($url)
  226. {
  227. $this->registerApi($url, __FUNCTION__, func_get_args());
  228. return $this->httpGetForJson($url);
  229. }
  230. }