国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
7.7KB

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