AccessToken.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. /**
  3. * This file is part of the apiadmin/tiktok.
  4. */
  5. namespace App\Libs\TikTok\MiniProgram\Kernel;
  6. use App\Libs\TikTok\Kernel\Contracts\AccessTokenInterface;
  7. use App\Libs\TikTok\Kernel\Exceptions\HttpException;
  8. use App\Libs\TikTok\Kernel\Exceptions\InvalidArgumentException;
  9. use App\Libs\TikTok\Kernel\Exceptions\InvalidConfigException;
  10. use App\Libs\TikTok\Kernel\Exceptions\RuntimeException;
  11. use App\Libs\TikTok\Kernel\ServiceContainer;
  12. use App\Libs\TikTok\Kernel\Support\Collection;
  13. use App\Libs\TikTok\Kernel\Traits\HasHttpRequests;
  14. use App\Libs\TikTok\Kernel\Traits\InteractsWithCache;
  15. use GuzzleHttp\Exception\GuzzleException;
  16. use GuzzleHttp\Psr7\Utils;
  17. use Psr\Http\Message\RequestInterface;
  18. use Psr\Http\Message\ResponseInterface;
  19. /**
  20. * Class AccessToken.
  21. *
  22. * @author overtrue <i@overtrue.me>
  23. */
  24. abstract class AccessToken implements AccessTokenInterface {
  25. use HasHttpRequests;
  26. use InteractsWithCache;
  27. /**
  28. * @var ServiceContainer
  29. */
  30. protected $app;
  31. /**
  32. * @var string
  33. */
  34. protected $requestMethod = 'POST';
  35. /**
  36. * @var string
  37. */
  38. protected $endpointToGetToken = 'apps/v2/token';
  39. /**
  40. * @var string
  41. */
  42. protected $queryName;
  43. /**
  44. * @var array
  45. */
  46. protected $token;
  47. /**
  48. * @var string
  49. */
  50. protected $tokenKey = 'access_token';
  51. /**
  52. * @var string
  53. */
  54. protected $headerKey = 'access-token';
  55. /**
  56. * @var string
  57. */
  58. protected $cachePrefix = 'EasyTiktok.mini_program.access_token.';
  59. /**
  60. * AccessToken constructor.
  61. *
  62. * @param ServiceContainer $app
  63. */
  64. public function __construct(ServiceContainer $app) {
  65. $this->app = $app;
  66. }
  67. /**
  68. * @param bool $refresh
  69. *
  70. * @return array
  71. *
  72. * @throws HttpException
  73. * @throws \Psr\SimpleCache\InvalidArgumentException
  74. * @throws InvalidConfigException
  75. * @throws InvalidArgumentException
  76. * @throws RuntimeException
  77. * @throws GuzzleException
  78. */
  79. public function getToken(bool $refresh = false): array {
  80. $cacheKey = $this->getCacheKey();
  81. $cache = $this->getCache();
  82. if (!$refresh && $cache->has($cacheKey) && $result = $cache->get($cacheKey)) {
  83. return $result;
  84. }
  85. /** @var array $token */
  86. $token = $this->requestToken($this->getCredentials(), true);
  87. $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200);
  88. return $token;
  89. }
  90. /**
  91. * @param string $token
  92. * @param int $lifetime
  93. *
  94. * @return AccessTokenInterface
  95. *
  96. * @throws RuntimeException
  97. * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
  98. */
  99. public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface {
  100. $this->getCache()->set($this->getCacheKey(), [
  101. $this->tokenKey => $token,
  102. 'expires_in' => $lifetime
  103. ], $lifetime);
  104. if (!$this->getCache()->has($this->getCacheKey())) {
  105. throw new RuntimeException('Failed to cache access token.');
  106. }
  107. return $this;
  108. }
  109. /**
  110. * @return AccessTokenInterface
  111. *
  112. * @throws HttpException
  113. * @throws \Psr\SimpleCache\InvalidArgumentException
  114. * @throws InvalidConfigException
  115. * @throws InvalidArgumentException
  116. * @throws RuntimeException|GuzzleException
  117. */
  118. public function refresh(): AccessTokenInterface {
  119. $this->getToken(true);
  120. return $this;
  121. }
  122. /**
  123. * @param array $credentials
  124. * @param bool $toArray
  125. *
  126. * @return ResponseInterface|Collection|array|object|string
  127. *
  128. * @throws HttpException
  129. * @throws InvalidConfigException
  130. * @throws InvalidArgumentException|GuzzleException
  131. */
  132. public function requestToken(array $credentials, bool $toArray) {
  133. $response = $this->sendRequest($credentials);
  134. $result = json_decode($response->getBody()->getContents(), true);
  135. $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type'));
  136. if (empty($result['data'][$this->tokenKey])) {
  137. throw new HttpException('Request access_token fail: ' . json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted);
  138. }
  139. return $toArray ? $result['data'] : $formatted;
  140. }
  141. /**
  142. * @param RequestInterface $request
  143. * @param array $requestOptions
  144. *
  145. * @return RequestInterface
  146. *
  147. * @throws HttpException
  148. * @throws \Psr\SimpleCache\InvalidArgumentException
  149. * @throws InvalidConfigException
  150. * @throws InvalidArgumentException
  151. * @throws RuntimeException|GuzzleException
  152. */
  153. public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface {
  154. parse_str($request->getUri()->getQuery(), $query);
  155. $query = http_build_query(array_merge($this->getQuery(), $query));
  156. return $request->withUri($request->getUri()->withQuery($query));
  157. }
  158. /**
  159. * 处理Post添加AccessToken
  160. * @param RequestInterface $request
  161. * @param array $requestOptions
  162. * @return RequestInterface
  163. * @throws GuzzleException
  164. * @throws HttpException
  165. * @throws InvalidArgumentException
  166. * @throws InvalidConfigException
  167. * @throws RuntimeException
  168. * @throws \Psr\SimpleCache\InvalidArgumentException
  169. * @author zhaoxiang <zhaoxiang051405@gmail.com>
  170. */
  171. public function applyToPostRequest(RequestInterface $request, array $requestOptions = []): RequestInterface {
  172. $query = $request->getBody()->getContents();
  173. $request->getBody()->rewind();
  174. $query = \GuzzleHttp\json_decode($query, true);
  175. $query = array_merge($this->getQuery(), $query);
  176. return $request->withBody(Utils::streamFor(json_encode($query)));
  177. }
  178. /**
  179. * 处理Header添加AccessToken
  180. * @param RequestInterface $request
  181. * @return RequestInterface
  182. * @throws GuzzleException
  183. * @throws HttpException
  184. * @throws InvalidArgumentException
  185. * @throws InvalidConfigException
  186. * @throws RuntimeException
  187. * @throws \Psr\SimpleCache\InvalidArgumentException
  188. */
  189. public function applyToHeaderRequest(RequestInterface $request): RequestInterface {
  190. $key = $this->headerKey ?: $this->tokenKey;
  191. return $request->withAddedHeader($key, $this->getToken()[$this->tokenKey]);
  192. }
  193. /**
  194. * Send http request.
  195. *
  196. * @param array $credentials
  197. *
  198. * @return ResponseInterface
  199. *
  200. * @throws InvalidArgumentException
  201. * @throws GuzzleException
  202. */
  203. protected function sendRequest(array $credentials): ResponseInterface {
  204. $options = [
  205. ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials,
  206. ];
  207. return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options);
  208. }
  209. /**
  210. *
  211. * @return string
  212. * @author zhaoxiang <zhaoxiang051405@gmail.com>
  213. */
  214. protected function getCacheKey(): string {
  215. return $this->cachePrefix . md5(json_encode($this->getCredentials()));
  216. }
  217. /**
  218. * The request query will be used to add to the request.
  219. *
  220. * @return array
  221. *
  222. * @throws HttpException
  223. * @throws \Psr\SimpleCache\InvalidArgumentException
  224. * @throws InvalidConfigException
  225. * @throws InvalidArgumentException
  226. * @throws RuntimeException|GuzzleException
  227. */
  228. protected function getQuery(): array {
  229. return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]];
  230. }
  231. /**
  232. * @return string
  233. *
  234. * @throws InvalidArgumentException
  235. */
  236. public function getEndpoint(): string {
  237. if (empty($this->endpointToGetToken)) {
  238. throw new InvalidArgumentException('No endpoint for access token request.');
  239. }
  240. return $this->endpointToGetToken;
  241. }
  242. /**
  243. * Credential for get token.
  244. *
  245. * @return array
  246. */
  247. abstract protected function getCredentials(): array;
  248. }