123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- <?php
- // +----------------------------------------------------------------------
- // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
- // +----------------------------------------------------------------------
- // | Author: CRMEB Team <admin@crmeb.com>
- // +----------------------------------------------------------------------
- namespace crmeb\services\easywechat\open3rd;
- use crmeb\exceptions\ApiException;
- use crmeb\services\HttpService;
- /**
- * Class AccessTokenServeService
- * @package crmeb\services
- */
- class AccessToken extends HttpService
- {
- /**
- * 第三方平台 appid
- * @var string
- */
- protected $component_appid;
- /**
- * 第三方平台 appsecret
- * @var string
- */
- protected $component_appsecret;
- /**
- * 微信后台推送的 ticket
- * @var string
- */
- protected $component_verify_ticket;
- /**
- * @var Cache|null
- */
- protected $cache;
- /**
- * 第三方平台token
- * @var string
- */
- protected $component_access_token;
- /**
- * 授权方appid
- * @var string
- */
- protected $authorizer_appid;
- /**
- * 接口调用令牌(在授权的公众号/小程序具备 API 权限时,才有此返回值)
- * @var string
- */
- protected $authorizer_access_token;
- /**
- * 刷新令牌(在授权的公众号具备API权限时,才有此返回值),刷新令牌主要用于第三方平台获取和刷新已授权用户的 authorizer_access_token。一旦丢失,只能让用户重新授权,才能再次拿到新的刷新令牌。用户重新授权后,之前的刷新令牌会失效
- * @var string
- */
- protected $authorizer_refresh_token;
- /**
- * @var string
- */
- protected $cacheTokenPrefix = "component_access_token_crmeb";
- /**
- * 获取第三方平台token
- * @var string
- */
- const TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/component/api_component_token';
- /**
- * 使用授权码获取授权信息
- */
- const AUTH_INFO = 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth';
- /**
- * 获取、刷新接口调用token
- */
- const AUTHORIZER_TOKEN = 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token';
- /**
- * AccessTokenServeService constructor.
- * @param string $component_appid
- * @param string $component_appsecret
- * @param string $component_verify_ticket
- * @param string $authorizer_appid
- * @param Cache|null $cache
- */
- public function __construct(string $component_appid, string $component_appsecret, string $component_verify_ticket, string $authorizer_appid = '', $cache = null)
- {
- if (!$cache) {
- $cache = app()->make(\crmeb\services\CacheService::class);
- }
- $this->component_appid = $component_appid;
- $this->component_appsecret = $component_appsecret;
- $this->component_verify_ticket = $component_verify_ticket;
- $this->authorizer_appid = $authorizer_appid;
- $this->cache = $cache;
- }
- /**
- * 获取配置
- * @return array
- */
- public function getConfig()
- {
- return [
- 'component_appid' => $this->component_appid,
- 'component_appsecret' => $this->component_appsecret,
- 'component_verify_ticket' => $this->component_verify_ticket,
- ];
- }
- /**
- * @return string
- */
- public function getComponentAppid()
- {
- return $this->component_appid;
- }
- /**
- * @return string
- */
- public function getComponentAppsecret()
- {
- return $this->component_appsecret;
- }
- /**
- * @return string
- */
- public function getAuthorizerAppid()
- {
- return $this->authorizer_appid;
- }
- /**
- * 获取第三方缓存token
- * @return mixed
- * @throws \Psr\SimpleCache\InvalidArgumentException
- */
- public function getComponentToken()
- {
- $accessTokenKey = md5($this->component_appid . '_' . $this->component_appid . '_' . $this->component_verify_ticket . '_' . $this->cacheTokenPrefix);
- $component_access_token = $this->cache->get($accessTokenKey);
- if (!$component_access_token) {
- $getToken = $this->getTokenFromServer();
- $this->cache->set($accessTokenKey, $getToken['component_access_token'], $getToken['expires_in'] ? $getToken['expires_in'] - 200 : 7000);
- $component_access_token = $getToken['component_access_token'];
- }
- $this->component_access_token = $component_access_token;
- return $component_access_token;
- }
- /**
- * 从服务器获取token
- * @return mixed
- */
- public function getTokenFromServer()
- {
- $config = $this->getConfig();
- if (!$config['component_appid'] || !$config['component_appsecret']) {
- throw new ApiException('请先配置第三方component_appid、component_appsecret');
- }
- if (!$config['component_verify_ticket']) {
- throw new ApiException('未配置微信开放平台或者未收到推送ticket,请等待10分钟后再试');
- }
- $res = $this->postRequest(self::TOKEN_URL, $config);
- $res = json_decode($res, true);
- if (!$res || $res['errcode'] != 0 || !isset($res['component_access_token']) || !$res['component_access_token']) {
- throw new ApiException('获取component_access_token失败,原因:' . $res['errmsg']);
- }
- return $res;
- }
- /**
- * 获取授权方token
- * @param $authorizer_appid
- * @return mixed
- */
- public function getAccessToken($authorizer_appid)
- {
- $accessTokenKey = md5('authorizer_access_token' . $authorizer_appid . '_' . $this->cacheTokenPrefix);
- $authorizer_access_token = $this->cache->get($accessTokenKey);
- if (!$authorizer_access_token) {
- $refreshTokenKey = md5('authorizer_refresh_token' . $authorizer_appid . '_' . $this->cacheTokenPrefix);
- $authorizer_refresh_token = $this->cache->get($refreshTokenKey);
- if (!$authorizer_refresh_token) {
- throw new ApiException('请重新授权');
- }
- $res = $this->freshAuthorizationToken($authorizer_appid, $authorizer_refresh_token);
- $this->cache->set(md5('authorizer_access_token' . $res['authorizer_appid'] . '_' . $this->cacheTokenPrefix), $res['authorizer_access_token'], $res['expires_in'] ? $res['expires_in'] - 200 : 7000);
- $this->cache->set(md5('authorizer_refrssh_token' . $res['authorizer_appid'] . '_' . $this->cacheTokenPrefix), $res['authorizer_refresh_token'], 30 * 24 * 3600);
- $authorizer_access_token = $res['authorizer_access_token'];
- }
- return $authorizer_access_token;
- }
- /**
- * 获取授权信息
- * @param $authorization_code 授权码
- * @return authorizer_appid string 授权方 appid
- * @return authorizer_access_token string 接口调用令牌(在授权的公众号/小程序具备 API 权限时,才有此返回值)
- * @return authorizer_refresh_token string 刷新令牌(在授权的公众号具备API权限时,才有此返回值),刷新令牌主要用于第三方平台获取和刷新已授权用户的 authorizer_access_token。一旦丢失,只能让用户重新授权,才能再次拿到新的刷新令牌。用户重新授权后,之前的刷新令牌会失效
- * @return array|bool|mixed
- */
- public function getAuthorizationInfo($authorization_code)
- {
- $res = $this->httpRequest(self::AUTH_INFO, ['authorization_code' => $authorization_code], false);
- if (!$res || $res['errcode'] != 0 || !isset($res['authorization_info']) || !$res['authorization_info']) {
- throw new ApiException('获取authorizer_access_token失败');
- }
- $res = $res['authorization_info'];
- $this->cache->set(md5('authorizer_access_token' . $res['authorizer_appid'] . '_' . $this->cacheTokenPrefix), $res['authorizer_access_token'], $res['expires_in'] ? $res['expires_in'] - 200 : 7000);
- //在授权的公众号具备API权限时,才有此返回值
- if (isset($res['authorizer_refrsh_token'])) {
- $this->cache->set(md5('authorizer_refresh_token' . $res['authorizer_appid'] . '_' . $this->cacheTokenPrefix), $res['authorizer_refrsh_token'], 30 * 24 * 3600);
- }
- return $res;
- }
- /**
- * 获取/刷新接口调用令牌
- * @param $authorizer_appid 授权方appid
- * @param $authorizer_refresh_token 刷新令牌,获取授权信息时得到
- * @return authorizer_access_token string 授权方令牌
- * @return authorizer_refresh_token string 刷新令牌
- * @return array|bool|mixed
- */
- public function freshAuthorizationToken($authorizer_appid, $authorizer_refresh_token)
- {
- $res = $this->postRequest(self::AUTHORIZER_TOKEN, ['component_access_token' => $this->component_access_token, 'authorizer_appid' => $authorizer_appid, 'authorizer_refresh_token' => $authorizer_refresh_token]);
- if (!$res || $res['errcode'] != 0 || !isset($res['authorizer_access_token']) || !$res['authorizer_access_token']) {
- throw new ApiException('刷新authorizer_access_token失败,请重新获取授权');
- }
- return $res;
- }
- /**
- * 请求
- * @param string $url
- * @param array $data
- * @param string $method
- * @param bool $isHeader
- * @return array|mixed
- */
- public function httpRequest(string $url, array $data = [], bool $is_atuh = true, string $method = 'POST')
- {
- if (!$is_atuh) {
- $this->getComponentToken();
- if (!$this->component_access_token) {
- throw new ApiException('配置已更改或component_access_token已失效');
- }
- $url .= '?component_access_token=' . $this->component_access_token;
- $data = array_merge($data, ['component_appid' => $this->component_appid]);
- } else {
- if (!$this->authorizer_appid) {
- throw new ApiException('缺少授权方authorizer_appid');
- }
- $access_token = $this->getAccessToken($this->authorizer_appid);
- if (!$access_token) {
- throw new ApiException('配置已更改或授权已失效请重新授权');
- }
- $url .= '?access_token=' . $access_token;
- }
- $res = $this->request($url, $method, $data);
- if (!$res) {
- throw new ApiException('请求微信服务器发生异常,请稍后重试');
- }
- return json_decode($res, true) ?: false;
- }
- /**
- * XML数据转换成array数组
- * @param string $xml
- * @return array
- */
- public function xmlToArray($xml)
- {
- // 禁止引用外部xml实体
- libxml_disable_entity_loader(true);
- $res = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
- return (array)$res;
- }
- /**
- * 对解密后的明文进行补位删除
- * @param decrypted 解密后的明文
- * @return 删除填充补位后的明文
- */
- public function decode($text)
- {
- $pad = ord(substr($text, -1));
- if ($pad < 1 || $pad > 32) {
- $pad = 0;
- }
- return substr($text, 0, (strlen($text) - $pad));
- }
- /**
- * 对密文进行解密
- * @param string $encodingAesKey 解密
- * @param string $encrypted 需要解密的密文
- * @return string 解密得到的明文
- */
- public function decrypt($encodingAesKey, $encrypted)
- {
- try {
- //使用BASE64对需要解密的字符串进行解码
- $ciphertext_dec = base64_decode($encrypted);
- $iv = substr(base64_decode($encodingAesKey . "="), 0, 16);
- $decrypted = openssl_decrypt($ciphertext_dec, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
- } catch (\Throwable $e) {
- throw new ApiException($e->getMessage());
- }
- try {
- //去除补位字符
- $result = $this->decode($decrypted);
- //去除16位随机字符串,网络字节序和AppId
- if (strlen($result) < 16)
- return "";
- $content = substr($result, 16, strlen($result));
- $len_list = unpack("N", substr($content, 0, 4));
- $xml_len = $len_list[1];
- $xml_content = substr($content, 4, $xml_len);
- } catch (\Throwable $e) {
- throw new ApiException($e->getMessage());
- }
- return $xml_content;
- }
- }
|