BaseClient.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. namespace App\Libs\TikTok\Kernel;
  3. use App\Libs\TikTok\Kernel\Contracts\AccessTokenInterface;
  4. use App\Libs\TikTok\Kernel\Exceptions\BadRequestException;
  5. use App\Libs\TikTok\Kernel\Exceptions\InvalidArgumentException;
  6. use App\Libs\TikTok\Kernel\Http\Response;
  7. use App\Libs\TikTok\Kernel\Support\Collection;
  8. use App\Libs\TikTok\Kernel\Traits\HasHttpRequests;
  9. use GuzzleHttp\Exception\GuzzleException;
  10. use GuzzleHttp\Middleware;
  11. use GuzzleHttp\Psr7\Utils;
  12. use Psr\Http\Message\RequestInterface;
  13. use Psr\Http\Message\ResponseInterface;
  14. use Closure;
  15. /**
  16. * Class BaseClient.
  17. *
  18. * @author overtrue <i@overtrue.me>
  19. */
  20. class BaseClient {
  21. use HasHttpRequests {
  22. request as performRequest;
  23. }
  24. /**
  25. * @var ServiceContainer
  26. */
  27. protected $app;
  28. /**
  29. * @var string
  30. */
  31. protected $baseUri;
  32. /**
  33. * @var bool 是否需要传递AccessToken
  34. */
  35. protected $needAccessToken = true;
  36. /**
  37. * @var bool
  38. */
  39. protected $postAccessToken = true;
  40. /**
  41. * @var bool
  42. */
  43. protected $headerAccessToken = false;
  44. /**
  45. * @var AccessTokenInterface
  46. */
  47. private $accessToken;
  48. /**
  49. * BaseClient constructor.
  50. * @param ServiceContainer $app
  51. * @param null $accessToken
  52. */
  53. public function __construct(ServiceContainer $app, $accessToken = null) {
  54. $this->app = $app;
  55. $this->accessToken = $accessToken ?? $this->app['access_token'];
  56. }
  57. /**
  58. * @param string $url
  59. * @param array $query
  60. * @return array|Collection|object|ResponseInterface|string
  61. * @throws Exceptions\HttpException
  62. * @throws Exceptions\InvalidConfigException
  63. * @throws GuzzleException
  64. */
  65. public function httpGet(string $url, array $query = []) {
  66. return $this->request($url, 'GET', ['query' => $query]);
  67. }
  68. /**
  69. * @param string $url
  70. * @param array $data
  71. * @return array|Collection|object|ResponseInterface|string
  72. * @throws Exceptions\HttpException
  73. * @throws Exceptions\InvalidConfigException
  74. * @throws GuzzleException
  75. */
  76. public function httpPost(string $url, array $data = []) {
  77. return $this->request($url, 'POST', ['form_params' => $data]);
  78. }
  79. /**
  80. * @param string $url
  81. * @param array $data
  82. * @return array|Collection|object|ResponseInterface|string
  83. * @throws Exceptions\HttpException
  84. * @throws Exceptions\InvalidConfigException
  85. * @throws GuzzleException
  86. */
  87. public function httpPostFormData(string $url, array $data = []) {
  88. $multipartData = [];
  89. foreach ($data as $key => $value) {
  90. $multipartData[] = [
  91. 'name' => $key,
  92. 'contents' => $value
  93. ];
  94. }
  95. return $this->request($url, 'POST', ['multipart' => $multipartData]);
  96. }
  97. /**
  98. * @param string $url
  99. * @param array $data
  100. * @param array $query
  101. * @return array
  102. * @throws Exceptions\HttpException
  103. * @throws Exceptions\InvalidConfigException
  104. * @throws GuzzleException
  105. */
  106. public function httpPostJson(string $url, array $data = [], array $query = []): array {
  107. return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
  108. }
  109. /**
  110. * 分片上传文件
  111. * @param string $url
  112. * @param string $file
  113. * @param array $query
  114. * @param int|float $chunkSize 每个分片的大小,不建议修改
  115. * @return array
  116. * @throws Exceptions\HttpException
  117. * @throws Exceptions\InvalidConfigException
  118. * @throws GuzzleException|BadRequestException|InvalidArgumentException
  119. * @author zhaoxiang <zhaoxiang051405@gmail.com>
  120. */
  121. public function httpChunkUpload(string $url, string $file, array $query = [], int $chunkSize = 1024 * 1024 * 10): array {
  122. $result = [];
  123. $fh = Utils::tryFopen($file, 'rb');
  124. $filesize = filesize($file);
  125. if ($filesize <= 1024 * 1024 * 5) {
  126. throw new InvalidArgumentException('The file size cannot be less than 5M');
  127. }
  128. $chunkNum = (int)round($filesize / $chunkSize);
  129. rewind($fh);
  130. $chunkIndex = 1;
  131. $tempPart = md5($query['upload_id']) . '.part';
  132. while ($chunkIndex <= $chunkNum) {
  133. $left = $filesize - ($chunkIndex - 1) * $chunkSize;
  134. $tempPartFile = dirname($file) . DIRECTORY_SEPARATOR . $tempPart . $chunkIndex;
  135. if (!is_writable($tempPartFile)) {
  136. throw new InvalidArgumentException("{$tempPartFile} can not be write");
  137. }
  138. file_put_contents($tempPartFile, $chunkIndex === $chunkNum ? fread($fh, $left) : fread($fh, $chunkSize));
  139. $multipart = [
  140. [
  141. 'name' => 'video',
  142. 'contents' => fopen($tempPartFile, 'rb')
  143. ]
  144. ];
  145. $query['part_number'] = $chunkIndex;
  146. $response = $this->request(
  147. $url,
  148. 'POST',
  149. ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 300, 'timeout' => 300, 'read_timeout' => 300]
  150. );
  151. if ($response['data']['error_code'] !== 0) {
  152. throw new BadRequestException($response['data']['description']);
  153. }
  154. @unlink($tempPartFile);
  155. $result[$chunkIndex] = $response;
  156. $chunkIndex++;
  157. }
  158. return $result;
  159. }
  160. /**
  161. * @param string $url
  162. * @param array $files
  163. * @param array $form
  164. * @param array $query
  165. * @return array|Collection|object|ResponseInterface|string
  166. * @throws Exceptions\HttpException
  167. * @throws Exceptions\InvalidConfigException
  168. * @throws GuzzleException
  169. */
  170. public function httpUpload(string $url, array $files = [], array $form = [], array $query = []) {
  171. $multipart = [];
  172. $headers = [];
  173. if (isset($form['filename'])) {
  174. $headers = [
  175. 'Content-Disposition' => 'form-data; name="media"; filename="' . $form['filename'] . '"'
  176. ];
  177. }
  178. foreach ($files as $name => $path) {
  179. $multipart[] = [
  180. 'name' => $name,
  181. 'contents' => fopen($path, 'r'),
  182. 'headers' => $headers
  183. ];
  184. }
  185. foreach ($form as $name => $contents) {
  186. $multipart[] = compact('name', 'contents');
  187. }
  188. return $this->request(
  189. $url,
  190. 'POST',
  191. ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 300, 'timeout' => 300, 'read_timeout' => 300]
  192. );
  193. }
  194. public function getAccessToken() {
  195. return $this->accessToken;
  196. }
  197. /**
  198. * @param AccessTokenInterface $accessToken
  199. *
  200. * @return $this
  201. */
  202. public function setAccessToken(AccessTokenInterface $accessToken): BaseClient {
  203. $this->accessToken = $accessToken;
  204. return $this;
  205. }
  206. /**
  207. *
  208. * @param string $url
  209. * @param string $method
  210. * @param array $options
  211. * @param false $returnRaw
  212. * @return array|Collection|object|ResponseInterface|string
  213. * @throws Exceptions\HttpException
  214. * @throws Exceptions\InvalidConfigException
  215. * @throws GuzzleException
  216. * @author zhaoxiang <zhaoxiang051405@gmail.com>
  217. */
  218. public function request(string $url, string $method = 'GET', array $options = [], bool $returnRaw = false) {
  219. if (empty($this->middlewares)) {
  220. $this->registerHttpMiddlewares();
  221. }
  222. $response = $this->performRequest($url, $method, $options);
  223. $this->app->events->dispatch(new Events\HttpResponseCreated($response));
  224. return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
  225. }
  226. /**
  227. * @param string $url
  228. * @param string $method
  229. * @param array $options
  230. * @return Response
  231. * @throws Exceptions\HttpException
  232. * @throws Exceptions\InvalidConfigException
  233. * @throws GuzzleException
  234. */
  235. public function requestRaw(string $url, string $method = 'GET', array $options = []): Response {
  236. return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
  237. }
  238. /**
  239. * Register Guzzle middlewares.
  240. */
  241. protected function registerHttpMiddlewares(): void {
  242. // retry
  243. $this->pushMiddleware($this->retryMiddleware(), 'retry');
  244. // access token
  245. if ($this->needAccessToken) {
  246. $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
  247. }
  248. }
  249. /**
  250. * Attache access token to request query.
  251. *
  252. * @return Closure
  253. */
  254. protected function accessTokenMiddleware(): Closure {
  255. return function(callable $handler) {
  256. return function(RequestInterface $request, array $options) use ($handler) {
  257. if ($this->accessToken) {
  258. if ($this->headerAccessToken) {
  259. $request = $this->accessToken->applyToHeaderRequest($request);
  260. }else{
  261. if ($this->postAccessToken) {
  262. $request = $this->accessToken->applyToPostRequest($request, $options);
  263. } else {
  264. $request = $this->accessToken->applyToRequest($request, $options);
  265. }
  266. }
  267. }
  268. return $handler($request, $options);
  269. };
  270. };
  271. }
  272. /**
  273. * Return retry middleware.
  274. *
  275. * @return Closure
  276. */
  277. protected function retryMiddleware(): Closure {
  278. return Middleware::retry(
  279. function($retries, RequestInterface $request, ResponseInterface $response = null) {
  280. if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
  281. $response = json_decode($body, true);
  282. if (
  283. (!empty($response['errcode']) && in_array(abs($response['errcode']), [40002], true)) ||
  284. (!empty($response['extra']['error_code']) && in_array(abs($response['extra']['error_code']), [2190008, 2190002, 10008], true))
  285. ) {
  286. $this->accessToken->refresh();
  287. return true;
  288. }
  289. }
  290. return false;
  291. },
  292. function() {
  293. return abs($this->app->config->get('http.retry_delay', 500));
  294. }
  295. );
  296. }
  297. }