22
33namespace App \Services ;
44
5+ use Closure ;
56use Exception ;
6- use App \Models \ConfigToken ;
77use Github \Client ;
8+ use App \Models \ConfigToken ;
9+ use Illuminate \Support \Arr ;
810use Github \HttpClient \Builder ;
911use Illuminate \Support \Facades \Log ;
10- use Http \Adapter \Guzzle6 \Client AS GuzzleClient ;
12+ use Http \Adapter \Guzzle6 \Client as GuzzleClient ;
13+ use GuzzleHttp \Handler \CurlHandler ;
14+ use GuzzleHttp \HandlerStack ;
15+ use GuzzleHttp \Middleware ;
16+ use GuzzleHttp \Psr7 \Request ;
17+ use GuzzleHttp \Psr7 \Response ;
18+ use GuzzleHttp \Exception \RequestException ;
1119
1220class GitHubService
1321{
22+ const HTTP_TIMEOUT = 30 ;
23+ const HTTP_DELAY = 2000 ;
24+ const HTTP_MAX_RETRIES = 5 ;
1425 const GET_CLIENT_TIMEOUT = 1800 ;
26+ const HTTP_CODE_UNAUTHORIZED = 401 ;
27+ const RATE_LIMIT_UNAUTHENTICATED = 10 ; // 未授权限制请求频率:10 次 / 分钟
28+
1529 public $ clients = [];
30+ private $ userAgent = 'Code6 ' ;
1631 private $ currentKey = -1 ;
1732
1833 public function __construct ()
1934 {
35+ $ this ->userAgent = config ('app.name ' );
2036 $ this ->createClients ();
2137 }
2238
@@ -40,18 +56,19 @@ public function getClient()
4056 $ client = &$ this ->clients [$ this ->currentKey ];
4157
4258 // 更新接口配额
43- if (time () >= $ client ['reset ' ]) {
59+ if (time () >= $ client ['api_reset_at ' ]) {
4460 $ this ->updateClient ($ client );
4561 }
4662
4763 // 当前客户端配额是否耗尽
48- if ($ client ['remaining ' ] <= 0 ) {
64+ if ($ client ['api_remaining ' ] <= 0 ) {
4965 sleep (1 );
5066 continue ;
5167 }
5268
5369 // 返回客户端
54- $ client ['remaining ' ]--;
70+ $ client ['api_remaining ' ]--;
71+ $ this ->updateConfigToken ($ client );
5572 return $ client ['client ' ];
5673 }
5774 }
@@ -63,8 +80,15 @@ private function createClients()
6380 {
6481 $ tokens = ConfigToken::inRandomOrder ()->get ()->pluck ('token ' );
6582 foreach ($ tokens as $ token ) {
83+ $ handlerStack = HandlerStack::create (new CurlHandler ());
84+ $ handlerStack ->push (Middleware::retry ($ this ->retryDecider ()));
85+ $ builder = new Builder (GuzzleClient::createWithConfig ([
86+ 'timeout ' => self ::HTTP_TIMEOUT ,
87+ 'delay ' => self ::HTTP_DELAY ,
88+ 'headers ' => ['User-Agent ' => $ this ->userAgent ],
89+ 'handler ' => $ handlerStack ,
90+ ]));
6691 $ client = ['token ' => $ token ];
67- $ builder = new Builder (GuzzleClient::createWithConfig (['timeout ' => 30 ]));
6892 $ client ['client ' ] = new Client ($ builder , 'v3.text-match ' );
6993 $ client ['client ' ]->authenticate ($ token , null , Client::AUTH_HTTP_TOKEN );
7094 if ($ this ->updateClient ($ client )) {
@@ -81,17 +105,60 @@ private function createClients()
81105 */
82106 private function updateClient (&$ client )
83107 {
108+ $ code = $ resource = null ;
84109 try {
85- if (!$ resource = $ client ['client ' ]->api ('rate_limit ' )->getResource ('search ' )) {
110+ $ resource = $ client ['client ' ]->api ('rate_limit ' )->getResource ('search ' );
111+ } catch (Exception $ e ) {
112+ $ code = $ e ->getCode ();
113+ Log::debug ($ e ->getMessage (), ['token ' => $ client ['token ' ]]);
114+ }
115+
116+ $ client ['api_limit ' ] = $ resource ? $ resource ->getLimit () : 0 ;
117+ $ client ['api_reset_at ' ] = $ resource ? $ resource ->getReset () : null ;
118+ $ client ['api_remaining ' ] = $ resource ? $ resource ->getRemaining () : 0 ;
119+ if ($ code == self ::HTTP_CODE_UNAUTHORIZED || $ client ['api_limit ' ] == self ::RATE_LIMIT_UNAUTHENTICATED ) {
120+ $ client ['status ' ] = ConfigToken::STATUS_ABNORMAL ;
121+ } else {
122+ $ client ['status ' ] = $ resource ? ConfigToken::STATUS_NORMAL : ConfigToken::STATUS_UNKNOWN ;
123+ }
124+
125+ $ this ->updateConfigToken ($ client );
126+ return $ client ['status ' ] == ConfigToken::STATUS_NORMAL ;
127+ }
128+
129+ /**
130+ * 更新数据库
131+ *
132+ * @param $client
133+ * @return mixed
134+ */
135+ private function updateConfigToken ($ client )
136+ {
137+ $ data = Arr::only ($ client , ['status ' , 'api_limit ' , 'api_remaining ' ]);
138+ $ data ['api_reset_at ' ] = $ client ['api_reset_at ' ] ? date ('Y-m-d H:i:s ' , $ client ['api_reset_at ' ]) : null ;
139+ return ConfigToken::where ('token ' , $ client ['token ' ])->update ($ data );
140+ }
141+
142+ /**
143+ * 决定是否重试请求
144+ *
145+ * @return Closure (true:重试 false:不重试)
146+ */
147+ protected function retryDecider ()
148+ {
149+ return function ($ retries , Request $ request , Response $ response = null , RequestException $ exception = null ) {
150+ // 最大次数
151+ if ($ retries >= self ::HTTP_MAX_RETRIES ) {
86152 return false ;
87153 }
88- $ client ['limit ' ] = $ resource ->getLimit ();
89- $ client ['reset ' ] = $ resource ->getReset ();
90- $ client ['remaining ' ] = $ resource ->getRemaining ();
91- } catch (Exception $ e ) {
92- Log::warning ($ e ->getMessage (), ['token ' => $ client ['token ' ]]);
154+
155+ // 请求失败
156+ if (!is_null ($ exception )) {
157+ Log::debug ('Retry request ' , ['exception ' => $ exception ->getMessage ()]);
158+ return true ;
159+ }
160+
93161 return false ;
94- }
95- return true ;
162+ };
96163 }
97164}
0 commit comments