2025-03-12 10:47:34 +08:00

347 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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