You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
13 KiB
347 lines
13 KiB
12 months ago
|
<?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;
|
||
|
}
|
||
|
}
|