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.
 
 
 
 
 
 

346 lines
13 KiB

<?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;
}
}