General.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. namespace App\libs\wechat\auth\Gateways;
  3. use App\libs\helpers\Curl;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\Facades\Redis;
  6. class General extends Base
  7. {
  8. private $appId;
  9. private $secret;
  10. public function __construct()
  11. {
  12. parent::__construct();
  13. $this->appId = $this->config['appid'];
  14. $this->secret = $this->config['secret'];
  15. }
  16. /**
  17. * @see 第一步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
  18. *
  19. * @desc 获取授权地址
  20. *
  21. * @param string $scope 授权类型snsapi_base|snsapi_userinfo
  22. * @param string|null $redirect 授权回跳地址
  23. */
  24. public function authUrl(string $scope = 'snsapi_base', string $redirect = null): string
  25. {
  26. $redirect = $redirect ?: "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
  27. $query = http_build_query([
  28. 'appid' => $this->appId,
  29. 'redirect_uri' => $redirect,
  30. 'response_type' => 'code',
  31. 'scope' => $scope,
  32. ]);
  33. return "https://open.weixin.qq.com/connect/oauth2/authorize?{$query}#wechat_redirect";
  34. }
  35. /**
  36. * @see 第二步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
  37. *
  38. * @desc 使用授权code换取openid
  39. */
  40. public function code2Openid(string $code): array
  41. {
  42. $url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
  43. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  44. 'appid' => $this->appId,
  45. 'secret' => $this->secret,
  46. 'code' => $code,
  47. 'grant_type' => 'authorization_code',
  48. ]));
  49. return $res ? json_decode($res, true) : [];
  50. }
  51. /**
  52. * @see 第四步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
  53. *
  54. * @desc 获取用户信息
  55. *
  56. * @return bool|mixed|string
  57. */
  58. public function userSimpleInfo(string $accessToken, string $openid): array
  59. {
  60. $url = 'https://api.weixin.qq.com/sns/userinfo';
  61. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  62. 'access_token' => $accessToken,
  63. 'openid' => $openid,
  64. 'lang' => 'zh_CN',
  65. ]));
  66. return $res ? json_decode($res, true) : [];
  67. }
  68. /**
  69. * @see https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
  70. *
  71. * @desc 获取用户详细信息
  72. */
  73. public function userDetailInfo(string $openid): array
  74. {
  75. $url = 'https://api.weixin.qq.com/cgi-bin/user/info';
  76. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  77. 'access_token' => $this->accessToken(),
  78. 'openid' => $openid,
  79. 'lang' => 'zh_CN',
  80. ]));
  81. return $res ? json_decode($res, true) : [];
  82. }
  83. /**
  84. * @see 附录1-JS-SDK使用权限签名算法 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
  85. *
  86. * @desc 获取 JSAPI TICKET
  87. *
  88. * @param int $repeatCnt
  89. */
  90. public function jsApiTicket(&$repeatCnt = 1): string
  91. {
  92. $drive = $this->config['token_drive'] ?? 'redis';
  93. $key = 'wechat_' . $this->appId . '_jsApiTicket';
  94. switch ($drive) {
  95. case 'redis':
  96. $redis = Redis::instance();
  97. $redis->select(15);
  98. $ticket = $redis->get($key);
  99. break;
  100. case 'db':
  101. default:
  102. $ticket = Db::table('configs')->where('c_key', $key)->where('c_expired > ' . time())->value('c_value');
  103. break;
  104. }
  105. if ($ticket) {
  106. return $ticket;
  107. }
  108. $url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket';
  109. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  110. 'access_token' => $this->accessToken(),
  111. 'type' => 'jsapi',
  112. ]));
  113. $data = json_decode($res, true);
  114. if (isset($data['ticket'])) {
  115. $ticket = $data['ticket'];
  116. switch ($drive) {
  117. case 'redis':
  118. $redis->setex($key, 7000, $ticket);
  119. break;
  120. case 'db':
  121. default:
  122. Db::table('configs')
  123. ->where('c_key', $key)
  124. ->update([
  125. 'c_value' => $ticket,
  126. 'c_expired' => time() + 7000,
  127. 'c_updated' => date('Y-m-d H:i:s'),
  128. ])
  129. ;
  130. break;
  131. }
  132. return $ticket;
  133. }
  134. if ($repeatCnt <= 3) {
  135. ++$repeatCnt;
  136. return $this->jsApiTicket($repeatCnt);
  137. }
  138. return $data['errcode'];
  139. }
  140. /**
  141. * @see https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
  142. *
  143. * @desc 获取全局唯一接口调用凭据
  144. *
  145. * @param int $repeatCnt
  146. *
  147. * @return string
  148. */
  149. public function accessToken(&$repeatCnt = 1)
  150. {
  151. $drive = $this->config['token_drive'] ?? 'redis';
  152. $key = 'wechat_' . $this->appId . '_accessToken';
  153. switch ($drive) {
  154. case 'redis':
  155. $redis = Redis::instance();
  156. $redis->select(15);
  157. $token = $redis->get($key);
  158. break;
  159. case 'db':
  160. default:
  161. $token = Db::table('configs')->where('c_key', $key)->where('c_expired', '>', time())->value('c_value');
  162. break;
  163. }
  164. if ($token) {
  165. return $token;
  166. }
  167. $url = 'https://api.weixin.qq.com/cgi-bin/token';
  168. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  169. 'grant_type' => 'client_credential',
  170. 'appid' => $this->appId,
  171. 'secret' => $this->secret,
  172. ]));
  173. $data = json_decode($res, true);
  174. if (isset($data['access_token'])) {
  175. $token = $data['access_token'];
  176. switch ($drive) {
  177. case 'redis':
  178. $redis->setex($key, 7000, $token);
  179. break;
  180. case 'db':
  181. default:
  182. $isWin = Db::table('configs')->where('c_key', $key)->value('c_value');
  183. if ($isWin) {
  184. Db::table('configs')
  185. ->where('c_key', $key)
  186. ->update([
  187. 'c_value' => $token,
  188. 'c_expired' => time() + 7000,
  189. 'c_updated' => date('Y-m-d H:i:s'),
  190. ])
  191. ;
  192. } else {
  193. Db::table('configs')
  194. ->insert([
  195. 'c_key' => $key,
  196. 'c_value' => $token,
  197. 'c_expired' => time() + 7000,
  198. 'c_updated' => date('Y-m-d H:i:s'),
  199. ])
  200. ;
  201. }
  202. break;
  203. }
  204. return $token;
  205. }
  206. if ($repeatCnt <= 3) {
  207. ++$repeatCnt;
  208. return $this->accessToken($repeatCnt);
  209. }
  210. return $data['errcode'];
  211. }
  212. }