mirror of
https://gitee.com/zoujingli/WeChatDeveloper.git
synced 2026-09-04 23:12:09 +08:00
refactor(core): 统一核心配置缓存命名与注释
- 统一根 Client 工厂为 wechatXXX/alipayXXX,保持与配置语义一致。 - 规范微信 platform、wxapp、service、payment 等缓存逻辑键命名。 - 补齐 Config、Contract、Exception 与缓存实现的中文 PHPDoc,提升开源项目可读性。
This commit is contained in:
parent
41e5265e08
commit
05cb47d3fd
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDK 根入口与通道客户端工厂。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We;
|
namespace We;
|
||||||
|
|
||||||
use GuzzleHttp\ClientInterface;
|
use GuzzleHttp\ClientInterface;
|
||||||
@ -26,10 +30,10 @@ use We\Platform\Wechat\WxappClient as WechatWxappClient;
|
|||||||
use We\Support\FileCacheStore;
|
use We\Support\FileCacheStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根入口:通过 `__call` 以通道工厂方法名创建各平台客户端(内部 `ReflectionClass::newInstanceArgs`),便于集中维护构造参数。
|
* SDK 根入口:按平台与业务域创建微信、支付宝客户端,工厂方法命名与配置对象语义保持一致。
|
||||||
*
|
*
|
||||||
* 缓存键固定为 `{cacheKeyPrefix}:{platformChannel}:{logicalKey}`,见 {@see \We\Support\CacheKey::compose};未传 `cacheKeyPrefix` 时使用 {@see self::DEFAULT_CACHE_KEY_PREFIX},不得传空白字符串。
|
* access_token 等运行态数据的缓存键固定为 `{cacheKeyPrefix}:{platformChannel}:{logicalKey}`,见 {@see \We\Support\CacheKey::compose}。
|
||||||
* 未传入 `cache` 时默认使用 {@see FileCacheStore},目录为 {@see self::defaultCacheStoreDirectory()}(位于 PHP `sys_get_temp_dir()` 下)。
|
* 未注入缓存实现时使用 {@see FileCacheStore},默认目录为 {@see self::defaultCacheStoreDirectory()}。
|
||||||
*
|
*
|
||||||
* @method WechatPlatformClient wechatPlatform(WechatPlatformConfig $config)
|
* @method WechatPlatformClient wechatPlatform(WechatPlatformConfig $config)
|
||||||
* @method WechatWxappClient wechatWxapp(WechatWxappConfig $config)
|
* @method WechatWxappClient wechatWxapp(WechatWxappConfig $config)
|
||||||
@ -54,6 +58,9 @@ final class Client
|
|||||||
|
|
||||||
private readonly string $cacheKeyPrefix;
|
private readonly string $cacheKeyPrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 SDK 根入口,注入运行态缓存、授权方 Token 仓库和 HTTP 客户端。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?StoreCacheInterface $cache = null,
|
?StoreCacheInterface $cache = null,
|
||||||
?StoreTokenInterface $authorizers = null,
|
?StoreTokenInterface $authorizers = null,
|
||||||
@ -78,27 +85,27 @@ final class Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按通道标识取实例:`new Client()->get('wechat.platform', new WechatPlatformConfig(...))`
|
* 按字符串通道创建客户端;通道名称与配置对象一一对应。
|
||||||
*/
|
*/
|
||||||
public function get(string $channel, ConfigInterface $config): object
|
public function get(string $channel, ConfigInterface $config): object
|
||||||
{
|
{
|
||||||
$factory = match ($channel) {
|
[$factory, $expected] = match ($channel) {
|
||||||
'wechat.platform' => 'wechatPlatform',
|
'wechat.platform' => ['wechatPlatform', WechatPlatformConfig::class],
|
||||||
'wechat.wxapp' => 'wechatWxapp',
|
'wechat.wxapp' => ['wechatWxapp', WechatWxappConfig::class],
|
||||||
'wechat.service' => 'wechatService',
|
'wechat.service' => ['wechatService', WechatServiceConfig::class],
|
||||||
'wechat.payment' => 'wechatPayment',
|
'wechat.payment' => ['wechatPayment', WechatPaymentConfig::class],
|
||||||
'alipay.platform' => 'alipayPlatform',
|
'alipay.platform' => ['alipayPlatform', AlipayPlatformConfig::class],
|
||||||
'alipay.payment' => 'alipayPayment',
|
'alipay.payment' => ['alipayPayment', AlipayPaymentConfig::class],
|
||||||
default => throw new WechatException('不支持的通道标识: ' . $channel),
|
default => throw new WechatException('不支持的通道标识: ' . $channel),
|
||||||
};
|
};
|
||||||
|
|
||||||
return $this->__call($factory, [$config]);
|
return $this->__call($factory, [$this->ensureConfig($config, $expected, $factory)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 魔术工厂:`$client->wechatPlatform($config)` 等价于反射 `new WechatPlatformClient(...)`。
|
* 魔术工厂:只暴露平台前缀明确的客户端创建方法。
|
||||||
*
|
*
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int,mixed> $arguments
|
||||||
*/
|
*/
|
||||||
public function __call(string $name, array $arguments): object
|
public function __call(string $name, array $arguments): object
|
||||||
{
|
{
|
||||||
@ -144,6 +151,8 @@ final class Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 使用反射创建具体平台客户端实例。
|
||||||
|
*
|
||||||
* @param class-string $class
|
* @param class-string $class
|
||||||
* @param array<int, mixed> $args
|
* @param array<int, mixed> $args
|
||||||
*/
|
*/
|
||||||
@ -157,6 +166,8 @@ final class Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 校验工厂方法收到的配置对象类型。
|
||||||
|
*
|
||||||
* @template T of object
|
* @template T of object
|
||||||
* @param class-string<T> $expected
|
* @param class-string<T> $expected
|
||||||
* @return T
|
* @return T
|
||||||
@ -170,7 +181,11 @@ final class Client
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param class-string $fqcn */
|
/**
|
||||||
|
* 获取类短名,用于生成清晰的配置类型错误信息。
|
||||||
|
*
|
||||||
|
* @param class-string $fqcn
|
||||||
|
*/
|
||||||
private function shortClass(string $fqcn): string
|
private function shortClass(string $fqcn): string
|
||||||
{
|
{
|
||||||
$pos = strrpos($fqcn, '\\');
|
$pos = strrpos($fqcn, '\\');
|
||||||
|
|||||||
@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付配置,继承支付宝开放平台网关签名与验签参数。
|
||||||
|
*/
|
||||||
final class AlipayPaymentConfig extends AlipayPlatformConfig
|
final class AlipayPaymentConfig extends AlipayPlatformConfig
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,16 +2,27 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝开放平台配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
use We\Contract\ConfigInterface;
|
use We\Contract\ConfigInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 支付宝开放平台基础配置。
|
||||||
|
*
|
||||||
|
* 应用私钥用于支付宝开放平台网关请求签名;支付宝公钥用于同步响应和异步通知验签。
|
||||||
|
*
|
||||||
* @phpstan-consistent-constructor
|
* @phpstan-consistent-constructor
|
||||||
*/
|
*/
|
||||||
class AlipayPlatformConfig implements ConfigInterface
|
class AlipayPlatformConfig implements ConfigInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建支付宝开放平台配置并执行必填项校验。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $appid,
|
public string $appid,
|
||||||
public string $privateKey,
|
public string $privateKey,
|
||||||
@ -25,6 +36,9 @@ class AlipayPlatformConfig implements ConfigInterface
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验支付宝 app_id 与应用私钥。
|
||||||
|
*/
|
||||||
public function validate(): void
|
public function validate(): void
|
||||||
{
|
{
|
||||||
if (trim($this->appid) === '' || trim($this->privateKey) === '') {
|
if (trim($this->appid) === '' || trim($this->privateKey) === '') {
|
||||||
@ -33,6 +47,8 @@ class AlipayPlatformConfig implements ConfigInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 从数组创建支付宝开放平台配置。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static
|
public static function fromArray(array $data): static
|
||||||
|
|||||||
@ -2,26 +2,44 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付 APIv3 商户配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
use We\Contract\ConfigInterface;
|
use We\Contract\ConfigInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付 APIv3 商户配置。
|
||||||
|
*
|
||||||
|
* 商户号、商户 API 证书序列号和商户私钥用于生成 APIv3 请求签名;APIv3 密钥用于回调资源解密;微信支付平台证书或平台公钥用于通知验签。
|
||||||
|
*/
|
||||||
final class WechatPaymentConfig implements ConfigInterface
|
final class WechatPaymentConfig implements ConfigInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建微信支付 APIv3 配置并执行必填项校验。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $appid,
|
public string $appid,
|
||||||
public string $mchId,
|
public string $mchId,
|
||||||
public string $apiV3Key,
|
public string $apiV3Key,
|
||||||
public string $merchantSerial,
|
public string $merchantSerial,
|
||||||
public string $merchantPrivateKey,
|
public string $merchantPrivateKey,
|
||||||
|
/** 微信支付平台证书 PEM;未配置 platformPublicKey 时用于回调验签。 */
|
||||||
public string $platformCertificate = '',
|
public string $platformCertificate = '',
|
||||||
|
/** 微信支付平台公钥 PEM,优先用于回调验签。 */
|
||||||
public string $platformPublicKey = '',
|
public string $platformPublicKey = '',
|
||||||
|
/** 微信支付平台证书/公钥序列号;非空时会校验回调头 Wechatpay-Serial。 */
|
||||||
public string $platformSerial = '',
|
public string $platformSerial = '',
|
||||||
) {
|
) {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验微信支付 appid、商户号、APIv3 密钥、商户证书序列号和商户私钥。
|
||||||
|
*/
|
||||||
public function validate(): void
|
public function validate(): void
|
||||||
{
|
{
|
||||||
foreach ([
|
foreach ([
|
||||||
@ -38,6 +56,8 @@ final class WechatPaymentConfig implements ConfigInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 从数组创建微信支付 APIv3 商户配置。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static
|
public static function fromArray(array $data): static
|
||||||
|
|||||||
@ -2,20 +2,32 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信公众平台配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
use We\Contract\ConfigInterface;
|
use We\Contract\ConfigInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信公众平台基础配置。
|
||||||
|
*
|
||||||
|
* appId/appSecret 用于获取微信公众平台接口调用凭据 access_token;Token 与 EncodingAESKey 用于服务器配置、消息签名校验和安全模式消息加解密。
|
||||||
|
*/
|
||||||
final class WechatPlatformConfig implements ConfigInterface
|
final class WechatPlatformConfig implements ConfigInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建微信公众平台配置并执行必填项校验。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $appid,
|
public string $appid,
|
||||||
public string $appSecret,
|
public string $appSecret,
|
||||||
public string $token = '',
|
public string $token = '',
|
||||||
public string $encodingAesKey = '',
|
public string $encodingAesKey = '',
|
||||||
/**
|
/**
|
||||||
* 业务维度的缓存分桶:Token 键形如 `wechat:app:{appid}:official:access_token:scope:{storageScope}`;
|
* 业务维度的缓存分桶:Token 键形如 `wechat:app:{appid}:platform:access_token:scope:{storageScope}`;
|
||||||
* 空字符串则仅按 appid 分组。多租户下同一 appid 若需隔离(极少见)可传租户/账号标识。
|
* 空字符串则仅按 appid 分组。多租户下同一 appid 若需隔离(极少见)可传租户/账号标识。
|
||||||
*/
|
*/
|
||||||
public string $storageScope = '',
|
public string $storageScope = '',
|
||||||
@ -24,6 +36,8 @@ final class WechatPlatformConfig implements ConfigInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 从数组创建微信公众平台配置,兼容常见下划线字段名。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static
|
public static function fromArray(array $data): static
|
||||||
@ -37,6 +51,9 @@ final class WechatPlatformConfig implements ConfigInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验微信公众平台 appid 与 appSecret。
|
||||||
|
*/
|
||||||
public function validate(): void
|
public function validate(): void
|
||||||
{
|
{
|
||||||
foreach ([
|
foreach ([
|
||||||
|
|||||||
@ -2,13 +2,25 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信服务平台(第三方平台)配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
use We\Contract\ConfigInterface;
|
use We\Contract\ConfigInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信服务平台(第三方平台)配置。
|
||||||
|
*
|
||||||
|
* componentAppid/componentAppSecret 用于获取第三方平台 component_access_token;componentToken/componentEncodingAesKey 用于授权事件接收 URL 的签名校验与安全模式消息解密。
|
||||||
|
*/
|
||||||
final class WechatServiceConfig implements ConfigInterface
|
final class WechatServiceConfig implements ConfigInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建微信服务平台(第三方平台)配置并执行必填项校验。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $componentAppid,
|
public string $componentAppid,
|
||||||
public string $componentAppSecret,
|
public string $componentAppSecret,
|
||||||
@ -20,6 +32,9 @@ final class WechatServiceConfig implements ConfigInterface
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验第三方平台 component_appid、secret、Token 与 EncodingAESKey。
|
||||||
|
*/
|
||||||
public function validate(): void
|
public function validate(): void
|
||||||
{
|
{
|
||||||
foreach ([
|
foreach ([
|
||||||
@ -35,6 +50,8 @@ final class WechatServiceConfig implements ConfigInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 从数组创建微信服务平台(第三方平台)配置。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static
|
public static function fromArray(array $data): static
|
||||||
|
|||||||
@ -2,13 +2,25 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序配置对象。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Config;
|
namespace We\Config;
|
||||||
|
|
||||||
use We\Contract\ConfigInterface;
|
use We\Contract\ConfigInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序基础配置。
|
||||||
|
*
|
||||||
|
* appid/appSecret 用于获取小程序接口调用凭据 access_token;storageScope 用于隔离不同业务上下文下的凭据缓存。
|
||||||
|
*/
|
||||||
final class WechatWxappConfig implements ConfigInterface
|
final class WechatWxappConfig implements ConfigInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建微信小程序配置并执行必填项校验。
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $appid,
|
public string $appid,
|
||||||
public string $appSecret,
|
public string $appSecret,
|
||||||
@ -19,6 +31,8 @@ final class WechatWxappConfig implements ConfigInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 从数组创建微信小程序配置,兼容常见下划线字段名。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static
|
public static function fromArray(array $data): static
|
||||||
@ -30,6 +44,9 @@ final class WechatWxappConfig implements ConfigInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验小程序 appid 与 appSecret。
|
||||||
|
*/
|
||||||
public function validate(): void
|
public function validate(): void
|
||||||
{
|
{
|
||||||
if (trim($this->appid) === '' || trim($this->appSecret) === '') {
|
if (trim($this->appid) === '' || trim($this->appSecret) === '') {
|
||||||
|
|||||||
@ -2,20 +2,26 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台配置契约。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Contract;
|
namespace We\Contract;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 平台配置统一契约:所有通道配置必须能从数组构造,并在构造阶段完成自身业务校验。
|
* 平台配置统一契约:所有配置对象必须支持数组构造,并在构造阶段完成平台必填项校验。
|
||||||
*/
|
*/
|
||||||
interface ConfigInterface
|
interface ConfigInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* 通过数组创建配置对象。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $data
|
* @param array<string,mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): static;
|
public static function fromArray(array $data): static;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验配置字段完整性、密钥格式等通道级约束;失败时抛出 SDK 异常。
|
* 校验平台配置的必填字段、密钥格式等约束;校验失败时抛出 SDK 异常。
|
||||||
*/
|
*/
|
||||||
public function validate(): void;
|
public function validate(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDK 运行态缓存契约。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Contract;
|
namespace We\Contract;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用缓存契约:用于 access_token 等 SDK 运行态数据,必须显式支持 TTL 与刷新锁。
|
* 运行态缓存契约:用于保存 access_token、component_access_token 等平台接口调用凭据,必须支持 TTL 与刷新锁。
|
||||||
*/
|
*/
|
||||||
interface StoreCacheInterface
|
interface StoreCacheInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 读取缓存值,缓存不存在、已过期或无法解析时返回默认值。
|
* 读取缓存值;缓存不存在、已过期或无法解析时返回默认值。
|
||||||
*/
|
*/
|
||||||
public function get(string $key, mixed $default = null): mixed;
|
public function get(string $key, mixed $default = null): mixed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入缓存值;$ttl 为剩余秒数,必须至少按 1 秒处理。
|
* 写入缓存值;TTL 单位为秒,实现侧至少按 1 秒处理。
|
||||||
*/
|
*/
|
||||||
public function set(string $key, mixed $value, int $ttl): void;
|
public function set(string $key, mixed $value, int $ttl): void;
|
||||||
|
|
||||||
@ -25,7 +29,7 @@ interface StoreCacheInterface
|
|||||||
public function del(string $key): void;
|
public function del(string $key): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在锁内执行回调,避免集群或多进程下重复刷新 access_token。
|
* 在互斥锁内执行回调,避免多进程或集群环境重复刷新平台接口调用凭据。
|
||||||
*
|
*
|
||||||
* @template T
|
* @template T
|
||||||
* @param callable():T $callback
|
* @param callable():T $callback
|
||||||
|
|||||||
@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信服务平台授权方 Token 存储契约。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Contract;
|
namespace We\Contract;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开放平台授权方 Token 存储契约;SDK 只读取 refresh token 并回写刷新结果。
|
* 微信服务平台授权方 Token 存储契约;SDK 读取 authorizer_refresh_token,并在刷新后回写授权方 Token 数据。
|
||||||
*/
|
*/
|
||||||
interface StoreTokenInterface
|
interface StoreTokenInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 获取授权账号 refresh token。
|
* 获取授权方账号的 authorizer_refresh_token。
|
||||||
*/
|
*/
|
||||||
public function refreshToken(string $authorizerAppid): string;
|
public function refreshToken(string $authorizerAppid): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权账号 token 刷新后回写业务存储,避免 SDK 持有请求态数据。
|
* 授权方 authorizer_access_token 刷新后回写业务存储。
|
||||||
*
|
*
|
||||||
* @param array<string,mixed> $payload
|
* @param array<string,mixed> $payload
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台接口请求异常。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Exception;
|
namespace We\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 请求失败或平台接口返回错误时抛出的异常。
|
||||||
|
*/
|
||||||
final class ApiException extends WechatException {}
|
final class ApiException extends WechatException {}
|
||||||
|
|||||||
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名与验签异常。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Exception;
|
namespace We\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信回调、微信支付通知或支付宝通知签名验证失败时抛出的异常。
|
||||||
|
*/
|
||||||
final class SignatureException extends WechatException {}
|
final class SignatureException extends WechatException {}
|
||||||
|
|||||||
@ -2,14 +2,25 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDK 基础异常。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Exception;
|
namespace We\Exception;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDK 基础异常。
|
||||||
|
*
|
||||||
|
* 通过 context() 暴露平台响应、验签明细等上下文,便于业务侧记录日志和排查问题。
|
||||||
|
*/
|
||||||
class WechatException extends RuntimeException
|
class WechatException extends RuntimeException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* 创建 SDK 异常并保存可选上下文。
|
||||||
|
*
|
||||||
* @param array<string,mixed> $context
|
* @param array<string,mixed> $context
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -22,6 +33,8 @@ class WechatException extends RuntimeException
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 获取异常附带的平台响应或验签上下文。
|
||||||
|
*
|
||||||
* @return array<string,mixed>
|
* @return array<string,mixed>
|
||||||
*/
|
*/
|
||||||
public function context(): array
|
public function context(): array
|
||||||
|
|||||||
@ -2,16 +2,22 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存键生成工具。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Support;
|
namespace We\Support;
|
||||||
|
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDK 缓存完整键生成器,固定为 `{cacheKeyPrefix}:{platformChannel}:{logicalKey}` 三段。
|
* SDK 缓存完整键生成器,固定为 `{cacheKeyPrefix}:{platformChannel}:{logicalKey}` 三段,便于按项目、通道和逻辑键隔离。
|
||||||
*/
|
*/
|
||||||
final class CacheKey
|
final class CacheKey
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* 组合缓存完整键。
|
||||||
|
*
|
||||||
* @param string $prefix 根 Client 通用命名段,不得为空,用于区分部署、租户或应用。
|
* @param string $prefix 根 Client 通用命名段,不得为空,用于区分部署、租户或应用。
|
||||||
* @param string $channel 通道段,如 `wechat.platform`,必须与 Client 通道标识一致。
|
* @param string $channel 通道段,如 `wechat.platform`,必须与 Client 通道标识一致。
|
||||||
* @param string $logicalKey 业务逻辑键,如 TokenCacheKey 生成的 access_token 键。
|
* @param string $logicalKey 业务逻辑键,如 TokenCacheKey 生成的 access_token 键。
|
||||||
@ -34,6 +40,9 @@ final class CacheKey
|
|||||||
return $prefix . ':' . $channel . ':' . $logical;
|
return $prefix . ':' . $channel . ':' . $logical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规范化缓存键命名段并去除两侧多余冒号。
|
||||||
|
*/
|
||||||
private static function normalizeSegment(string $segment): string
|
private static function normalizeSegment(string $segment): string
|
||||||
{
|
{
|
||||||
$segment = trim($segment);
|
$segment = trim($segment);
|
||||||
|
|||||||
@ -2,16 +2,23 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地文件缓存实现。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Support;
|
namespace We\Support;
|
||||||
|
|
||||||
use We\Contract\StoreCacheInterface;
|
use We\Contract\StoreCacheInterface;
|
||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单机文件缓存:缓存值以 JSON 保存,使用 flock 提供单机多进程刷新锁。
|
* 本地文件缓存实现:缓存值以 JSON 保存,并使用 flock 提供单机多进程刷新锁。
|
||||||
*/
|
*/
|
||||||
final class FileCacheStore implements StoreCacheInterface
|
final class FileCacheStore implements StoreCacheInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 创建文件缓存目录并校验可写权限。
|
||||||
|
*/
|
||||||
public function __construct(private readonly string $directory)
|
public function __construct(private readonly string $directory)
|
||||||
{
|
{
|
||||||
if ($this->directory === '') {
|
if ($this->directory === '') {
|
||||||
@ -25,6 +32,9 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取文件缓存值;文件不存在、过期或 JSON 无效时返回默认值。
|
||||||
|
*/
|
||||||
public function get(string $key, mixed $default = null): mixed
|
public function get(string $key, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
$path = $this->pathFor($key);
|
$path = $this->pathFor($key);
|
||||||
@ -55,7 +65,7 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
if (!is_array($payload) || !array_key_exists('expires_at', $payload) || !array_key_exists('value', $payload)) {
|
if (!is_array($payload) || !array_key_exists('expires_at', $payload) || !array_key_exists('value', $payload)) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
if ((int)$payload['expires_at'] < time()) {
|
if ((int)$payload['expires_at'] <= time()) {
|
||||||
$this->del($key);
|
$this->del($key);
|
||||||
|
|
||||||
return $default;
|
return $default;
|
||||||
@ -64,6 +74,9 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
return $payload['value'];
|
return $payload['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以原子写入方式保存缓存值与过期时间。
|
||||||
|
*/
|
||||||
public function set(string $key, mixed $value, int $ttl): void
|
public function set(string $key, mixed $value, int $ttl): void
|
||||||
{
|
{
|
||||||
$path = $this->pathFor($key);
|
$path = $this->pathFor($key);
|
||||||
@ -90,6 +103,9 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定缓存键对应的文件。
|
||||||
|
*/
|
||||||
public function del(string $key): void
|
public function del(string $key): void
|
||||||
{
|
{
|
||||||
$path = $this->pathFor($key);
|
$path = $this->pathFor($key);
|
||||||
@ -98,6 +114,9 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 flock 在互斥锁内执行回调。
|
||||||
|
*/
|
||||||
public function lock(string $key, int $ttl, callable $callback): mixed
|
public function lock(string $key, int $ttl, callable $callback): mixed
|
||||||
{
|
{
|
||||||
$path = $this->pathFor('lock:' . $key) . '.lock';
|
$path = $this->pathFor('lock:' . $key) . '.lock';
|
||||||
@ -123,6 +142,9 @@ final class FileCacheStore implements StoreCacheInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据缓存键生成哈希分片后的文件路径。
|
||||||
|
*/
|
||||||
private function pathFor(string $key): string
|
private function pathFor(string $key): string
|
||||||
{
|
{
|
||||||
$hash = hash('sha256', $key);
|
$hash = hash('sha256', $key);
|
||||||
|
|||||||
@ -2,24 +2,40 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空缓存实现。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Support;
|
namespace We\Support;
|
||||||
|
|
||||||
use We\Contract\StoreCacheInterface;
|
use We\Contract\StoreCacheInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 空缓存实现:用于测试或显式禁用缓存,锁回调直接执行。
|
* 空缓存实现:用于测试或显式禁用缓存;读取永远返回默认值,锁回调直接执行。
|
||||||
*/
|
*/
|
||||||
final class NullCacheStore implements StoreCacheInterface
|
final class NullCacheStore implements StoreCacheInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 返回默认值,不保存任何缓存数据。
|
||||||
|
*/
|
||||||
public function get(string $key, mixed $default = null): mixed
|
public function get(string $key, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略写入请求。
|
||||||
|
*/
|
||||||
public function set(string $key, mixed $value, int $ttl): void {}
|
public function set(string $key, mixed $value, int $ttl): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略删除请求。
|
||||||
|
*/
|
||||||
public function del(string $key): void {}
|
public function del(string $key): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不加锁,直接执行回调。
|
||||||
|
*/
|
||||||
public function lock(string $key, int $ttl, callable $callback): mixed
|
public function lock(string $key, int $ttl, callable $callback): mixed
|
||||||
{
|
{
|
||||||
return $callback();
|
return $callback();
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PSR-16 缓存适配器。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Support;
|
namespace We\Support;
|
||||||
|
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
@ -9,11 +13,13 @@ use We\Contract\StoreCacheInterface;
|
|||||||
use We\Exception\WechatException;
|
use We\Exception\WechatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PSR-16 缓存适配器;集群锁需由业务注入原子锁回调。
|
* PSR-16 缓存适配器;缓存能力委托给 PSR Simple Cache,实现侧需注入原子锁回调以支持刷新锁。
|
||||||
*/
|
*/
|
||||||
final class PsrSimpleCacheStore implements StoreCacheInterface
|
final class PsrSimpleCacheStore implements StoreCacheInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* 创建 PSR-16 缓存适配器,并可选注入锁回调。
|
||||||
|
*
|
||||||
* @param null|callable(string,int,callable):mixed $locker
|
* @param null|callable(string,int,callable):mixed $locker
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -21,21 +27,33 @@ final class PsrSimpleCacheStore implements StoreCacheInterface
|
|||||||
private readonly mixed $locker = null,
|
private readonly mixed $locker = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 PSR-16 缓存读取值。
|
||||||
|
*/
|
||||||
public function get(string $key, mixed $default = null): mixed
|
public function get(string $key, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
return $this->cache->get($key, $default);
|
return $this->cache->get($key, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入 PSR-16 缓存并设置 TTL。
|
||||||
|
*/
|
||||||
public function set(string $key, mixed $value, int $ttl): void
|
public function set(string $key, mixed $value, int $ttl): void
|
||||||
{
|
{
|
||||||
$this->cache->set($key, $value, max(1, $ttl));
|
$this->cache->set($key, $value, max(1, $ttl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 PSR-16 缓存删除值。
|
||||||
|
*/
|
||||||
public function del(string $key): void
|
public function del(string $key): void
|
||||||
{
|
{
|
||||||
$this->cache->delete($key);
|
$this->cache->delete($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过业务注入的锁回调执行互斥逻辑。
|
||||||
|
*/
|
||||||
public function lock(string $key, int $ttl, callable $callback): mixed
|
public function lock(string $key, int $ttl, callable $callback): mixed
|
||||||
{
|
{
|
||||||
if (!is_callable($this->locker)) {
|
if (!is_callable($this->locker)) {
|
||||||
|
|||||||
@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台接口调用凭据缓存逻辑键生成工具。
|
||||||
|
*/
|
||||||
|
|
||||||
namespace We\Support;
|
namespace We\Support;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token 缓存键统一按「微信侧 appid 优先」分段,便于 Redis 等按前缀扫描、按应用清理。
|
* 微信接口调用凭据缓存逻辑键统一按「微信侧 appid 优先」分段,便于 Redis 等存储按应用前缀扫描和清理。
|
||||||
*
|
*
|
||||||
* 约定前缀:`wechat:app:{appid}:...`;若 Config 带 `storageScope`(业务隔离),追加 `:scope:{值}`。
|
* 约定前缀:`wechat:app:{appid}:...`;若 Config 带 `storageScope`(业务隔离),追加 `:scope:{值}`。
|
||||||
* 经根 {@see \We\Client} 与各通道写入存储时,作为第三段与通用前缀、平台段经 {@see CacheKey::compose} 拼成固定三段键。
|
* 经根 {@see \We\Client} 与各通道写入存储时,作为第三段与通用前缀、平台段经 {@see CacheKey::compose} 拼成固定三段键。
|
||||||
@ -13,41 +17,44 @@ namespace We\Support;
|
|||||||
final class TokenCacheKey
|
final class TokenCacheKey
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 公众号 client_credential access_token
|
* 微信公众平台 client_credential access_token。
|
||||||
* 例:`wechat:app:wxabcd:official:access_token` 或带 scope 后缀。
|
* 例:`wechat:app:wxabcd:platform:access_token` 或带 scope 后缀。
|
||||||
*/
|
*/
|
||||||
public static function wechatOfficialAccessToken(string $appid, string $storageScope = ''): string
|
public static function wechatPlatformAccessToken(string $appid, string $storageScope = ''): string
|
||||||
{
|
{
|
||||||
return self::appendScope('wechat:app:' . $appid . ':official:access_token', $storageScope);
|
return self::appendScope('wechat:app:' . $appid . ':platform:access_token', $storageScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小程序 client_credential access_token
|
* 小程序 client_credential access_token。
|
||||||
*/
|
*/
|
||||||
public static function wechatMiniAccessToken(string $appid, string $storageScope = ''): string
|
public static function wechatWxappAccessToken(string $appid, string $storageScope = ''): string
|
||||||
{
|
{
|
||||||
return self::appendScope('wechat:app:' . $appid . ':mini:access_token', $storageScope);
|
return self::appendScope('wechat:app:' . $appid . ':wxapp:access_token', $storageScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开放平台第三方 component_access_token(按 component_appid 分组)
|
* 微信服务平台(第三方平台) component_access_token(按 component_appid 分组)。
|
||||||
*/
|
*/
|
||||||
public static function wechatOpenComponentAccessToken(string $componentAppid, string $storageScope = ''): string
|
public static function wechatServiceComponentAccessToken(string $componentAppid, string $storageScope = ''): string
|
||||||
{
|
{
|
||||||
return self::appendScope('wechat:app:' . $componentAppid . ':open:component_access_token', $storageScope);
|
return self::appendScope('wechat:app:' . $componentAppid . ':service:component_access_token', $storageScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开放平台代授权方 authorizer_access_token(挂在 component_appid 下,避免键平面冲突)
|
* 微信服务平台代授权方 authorizer_access_token(挂在 component_appid 下,避免键平面冲突)。
|
||||||
*/
|
*/
|
||||||
public static function wechatOpenAuthorizerAccessToken(string $componentAppid, string $authorizerAppid, string $storageScope = ''): string
|
public static function wechatServiceAuthorizerAccessToken(string $componentAppid, string $authorizerAppid, string $storageScope = ''): string
|
||||||
{
|
{
|
||||||
return self::appendScope(
|
return self::appendScope(
|
||||||
'wechat:app:' . $componentAppid . ':open:authorizer:' . $authorizerAppid . ':access_token',
|
'wechat:app:' . $componentAppid . ':service:authorizer:' . $authorizerAppid . ':access_token',
|
||||||
$storageScope,
|
$storageScope,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按需追加 storageScope,隔离同一 appid 在不同业务上下文中的凭据缓存。
|
||||||
|
*/
|
||||||
private static function appendScope(string $base, string $storageScope): string
|
private static function appendScope(string $base, string $storageScope): string
|
||||||
{
|
{
|
||||||
$s = trim($storageScope);
|
$s = trim($storageScope);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user