refactor(core): 统一核心配置缓存命名与注释

- 统一根 Client 工厂为 wechatXXX/alipayXXX,保持与配置语义一致。

- 规范微信 platform、wxapp、service、payment 等缓存逻辑键命名。

- 补齐 Config、Contract、Exception 与缓存实现的中文 PHPDoc,提升开源项目可读性。
This commit is contained in:
Anyon 2026-05-08 00:29:04 +08:00
parent 41e5265e08
commit 05cb47d3fd
18 changed files with 266 additions and 44 deletions

View File

@ -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, '\\');

View File

@ -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
{ {
} }

View File

@ -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

View File

@ -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

View File

@ -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_tokenToken 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 ([

View File

@ -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_tokencomponentToken/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

View File

@ -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_tokenstorageScope 用于隔离不同业务上下文下的凭据缓存。
*/
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) === '') {

View File

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

View File

@ -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

View File

@ -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
*/ */

View File

@ -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 {}

View File

@ -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 {}

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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)) {

View File

@ -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);