BaseClient.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. /*
  8. * This file is part of the overtrue/wechat.
  9. *
  10. * (c) overtrue <i@overtrue.me>
  11. *
  12. * This source file is subject to the MIT license that is bundled
  13. * with this source code in the file LICENSE.
  14. */
  15. namespace ByteDance\Kernel;
  16. use GuzzleHttp\MessageFormatter;
  17. use GuzzleHttp\Middleware;
  18. use ByteDance\Kernel\Contracts\AccessTokenInterface;
  19. use ByteDance\Kernel\Http\Response;
  20. use ByteDance\Kernel\Traits\HasHttpRequests;
  21. use Psr\Http\Message\RequestInterface;
  22. use Psr\Http\Message\ResponseInterface;
  23. /**
  24. * Class BaseClient.
  25. *
  26. * @author overtrue <i@overtrue.me>
  27. */
  28. class BaseClient
  29. {
  30. use HasHttpRequests { request as performRequest; }
  31. /**
  32. * @var \ByteDance\Kernel\ServiceContainer
  33. */
  34. protected $app;
  35. /**
  36. * @var \ByteDance\Kernel\Contracts\AccessTokenInterface
  37. */
  38. protected $accessToken;
  39. /**
  40. * @var
  41. */
  42. protected $baseUri;
  43. /**
  44. * BaseClient constructor.
  45. *
  46. * @param \ByteDance\Kernel\ServiceContainer $app
  47. * @param \ByteDance\Kernel\Contracts\AccessTokenInterface|null $accessToken
  48. */
  49. public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null)
  50. {
  51. $this->app = $app;
  52. $this->accessToken = $accessToken ?? $this->app['access_token'];
  53. }
  54. /**
  55. * GET request.
  56. *
  57. * @param string $url
  58. * @param array $query
  59. *
  60. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  61. *
  62. * @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string
  63. */
  64. public function httpGet(string $url, array $query = [])
  65. {
  66. return $this->request($url, 'GET', ['query' => $query]);
  67. }
  68. /**
  69. * POST request.
  70. *
  71. * @param string $url
  72. * @param array $data
  73. *
  74. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  75. *
  76. * @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string
  77. */
  78. public function httpPost(string $url, array $data = [])
  79. {
  80. return $this->request($url, 'POST', ['form_params' => $data]);
  81. }
  82. /**
  83. * JSON request.
  84. *
  85. * @param string $url
  86. * @param string|array $data
  87. * @param array $query
  88. *
  89. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  90. *
  91. * @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string
  92. */
  93. public function httpPostJson(string $url, array $data = [], array $query = [])
  94. {
  95. return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
  96. }
  97. /**
  98. * Upload file.
  99. *
  100. * @param string $url
  101. * @param array $files
  102. * @param array $form
  103. * @param array $query
  104. *
  105. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  106. *
  107. * @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string
  108. */
  109. public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
  110. {
  111. $multipart = [];
  112. foreach ($files as $name => $path) {
  113. $multipart[] = [
  114. 'name' => $name,
  115. 'contents' => fopen($path, 'r'),
  116. ];
  117. }
  118. foreach ($form as $name => $contents) {
  119. $multipart[] = compact('name', 'contents');
  120. }
  121. return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]);
  122. }
  123. /**
  124. * @return AccessTokenInterface
  125. */
  126. public function getAccessToken(): AccessTokenInterface
  127. {
  128. return $this->accessToken;
  129. }
  130. /**
  131. * @param \ByteDance\Kernel\Contracts\AccessTokenInterface $accessToken
  132. *
  133. * @return $this
  134. */
  135. public function setAccessToken(AccessTokenInterface $accessToken)
  136. {
  137. $this->accessToken = $accessToken;
  138. return $this;
  139. }
  140. /**
  141. * @param string $url
  142. * @param string $method
  143. * @param array $options
  144. * @param bool $returnRaw
  145. *
  146. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  147. *
  148. * @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string
  149. */
  150. public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
  151. {
  152. if (empty($this->middlewares)) {
  153. $this->registerHttpMiddlewares();
  154. }
  155. $response = $this->performRequest($url, $method, $options);
  156. return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
  157. }
  158. /**
  159. * @param string $url
  160. * @param string $method
  161. * @param array $options
  162. *
  163. * @throws \ByteDance\Kernel\Exceptions\InvalidConfigException
  164. *
  165. * @return \ByteDance\Kernel\Http\Response
  166. */
  167. public function requestRaw(string $url, string $method = 'GET', array $options = [])
  168. {
  169. return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
  170. }
  171. /**
  172. * Register Guzzle middlewares.
  173. */
  174. protected function registerHttpMiddlewares()
  175. {
  176. // retry
  177. $this->pushMiddleware($this->retryMiddleware(), 'retry');
  178. // access token
  179. $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
  180. // log
  181. $this->pushMiddleware($this->logMiddleware(), 'log');
  182. }
  183. /**
  184. * Attache access token to request query.
  185. *
  186. * @return \Closure
  187. */
  188. protected function accessTokenMiddleware()
  189. {
  190. return function (callable $handler) {
  191. return function (RequestInterface $request, array $options) use ($handler) {
  192. if ($this->accessToken) {
  193. $request = $this->accessToken->applyToRequest($request, $options);
  194. }
  195. return $handler($request, $options);
  196. };
  197. };
  198. }
  199. /**
  200. * Log the request.
  201. *
  202. * @return \Closure
  203. */
  204. protected function logMiddleware()
  205. {
  206. $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
  207. return Middleware::log($this->app['logger'], $formatter);
  208. }
  209. /**
  210. * Return retry middleware.
  211. *
  212. * @return \Closure
  213. */
  214. protected function retryMiddleware()
  215. {
  216. return Middleware::retry(function (
  217. $retries,
  218. RequestInterface $request,
  219. ResponseInterface $response = null
  220. ) {
  221. // Limit the number of retries to 2
  222. if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
  223. // Retry on server errors
  224. $response = json_decode($body, true);
  225. if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
  226. $this->accessToken->refresh();
  227. $this->app['logger']->debug('Retrying with refreshed access token.');
  228. return true;
  229. }
  230. }
  231. return false;
  232. }, function () {
  233. return abs($this->app->config->get('http.retry_delay', 500));
  234. });
  235. }
  236. }