You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel/Lumen中如何编程实现Guzzle指定缓存目录失效刷新

问题场景

我在PHP ^8.1.3环境的Lumen ^9.0项目(底层机制与Laravel一致)中,使用guzzlehttp/guzzle ^7.4作为HTTP客户端,搭配kevinrob/guzzle-cache-middleware ^4.0、symfony/cache ^6.1组件实现请求文件缓存。
缓存按业务分目录存放,例如书籍价格接口https://example.com/books/prices的缓存路径为framework/cache/data/GuzzleFileCache/public/books/prices,缓存TTL为1小时,调用示例如下:

curl -X GET https://example.com/books/prices 

若后台提前更新了书籍价格,缓存TTL未过期时接口会返回旧价格;我已实现价格更新时触发事件通知主服务的逻辑,需要在事件触发时主动失效对应路径的缓存。
当前缓存初始化配置代码如下:

$ttl = 3600;
// 创建HandlerStack实例
$stack = HandlerStack::create();
// 指定缓存根目录名
$requestCacheFolderName = 'framework/cache/data';
// 指定业务缓存子路径
$cacheFolderPath = 'GuzzleFileCache/public/blogs';

// 实例化基于PSR-6规范的文件缓存存储
$cache_storage = new Psr6CacheStorage(
    new FilesystemAdapter(
        $requestCacheFolderName,
        $ttl,
        $cacheFolderPath
    )
);

// 挂载缓存中间件到调用栈
$stack->push(
    new CacheMiddleware(
        new GreedyCacheStrategy(
            $cache_storage,
            $ttl // TTL单位为秒
        )
    ),
    'greedy-cache'
);
核心诉求

需要支持编程式定向清除/刷新指定业务路径(如framework/cache/data/GuzzleFileCache/public/blogs、framework/cache/data/GuzzleFileCache/public/books/prices)下的Guzzle缓存。目前已知可通过删除目录的粗暴方式实现:

rmdir('framework/cache/data/GuzzleFileCache/public/blogs');

但需要更规范的实现方案,达到类似下面向调用的效果,无需手动操作文件系统删除目录:

use Illuminate\Support\Facades\Http; // Laravel/Lumen封装的Guzzle客户端

Http::cacheInvalidate('framework/cache/data/GuzzleFileCache/public/blogs');

项目依赖

{
    "php": "^8.1.3",
    "flipbox/lumen-generator": "^9.1",
    "guzzlehttp/guzzle": "^7.4", 
    "kevinrob/guzzle-cache-middleware": "^4.0",
    "laravel/lumen-framework": "^9.0",
    "symfony/cache": "^6.1"
}

实现方案

不要直接通过rmdir删除缓存目录,Symfony文件缓存组件维护了统一的缓存元数据索引,手动删目录会导致元数据与实际文件不一致,引发缓存失效异常、命中失败等问题。可以通过以下步骤实现规范的定向缓存清除:

  • 第一步:将缓存实例注册为容器单例,全局复用
    原有代码每次初始化Guzzle客户端都会新建缓存实例,后续清除缓存时无法拿到对应实例操作。可以在服务提供者或bootstrap/app.php中绑定缓存单例,注意修正原有代码中FilesystemAdapter的参数传值错误(原代码把根目录和命名空间参数传反了):
$defaultTtl = 3600;
$cacheRootPath = storage_path('framework/cache/data');

$this->app->singleton('guzzle.cache.store', function () use ($defaultTtl, $cacheRootPath) {
    return new FilesystemAdapter(
        '', // 留空默认命名空间,方便后续按前缀批量清除
        $defaultTtl,
        $cacheRootPath
    );
});
  • 第二步:调整Guzzle客户端初始化逻辑,复用容器内的缓存实例
    初始化Guzzle调用栈时直接从容器取缓存实例,避免重复创建:
$stack = HandlerStack::create();
$cacheStorage = new Psr6CacheStorage(app('guzzle.cache.store'));
$stack->push(new CacheMiddleware(new GreedyCacheStrategy($cacheStorage, $ttl)), 'greedy-cache');
// 将$stack传入Guzzle客户端实例完成初始化即可
  • 第三步:给Http门面注册缓存清除宏,实现预期调用效果
    在服务提供者的boot方法中注册cacheInvalidate宏,底层调用Symfony缓存的原生前缀清除能力,无需手动操作文件:
use Illuminate\Support\Facades\Http;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

Http::macro('cacheInvalidate', function (string $businessPath, string $baseUri = 'https://example.com/') {
    /** @var FilesystemAdapter $cacheStore */
    $cacheStore = app('guzzle.cache.store');
    // 从传入的业务路径中解析实际接口路径
    $apiPath = trim(str_replace('framework/cache/data/GuzzleFileCache/public/', '', $businessPath), '/');
    $targetUri = rtrim($baseUri, '/') . '/' . $apiPath;
    // 生成该路径对应的缓存key前缀,前缀规则与guzzle-cache-middleware内部生成逻辑一致
    $keyPrefix = CacheMiddleware::CACHE_KEY_PREFIX . sha1('GET' . $targetUri);
    // 调用Symfony缓存原生清除方法,自动维护元数据一致性
    $cacheStore->clear($keyPrefix);
    return true;
});
  • 第四步:业务侧直接调用即可
    在价格更新的事件监听器、其他需要失效缓存的业务场景中,直接按预期方式调用:
// 清除博客模块所有接口缓存
Http::cacheInvalidate('framework/cache/data/GuzzleFileCache/public/blogs');
// 清除书籍价格接口缓存
Http::cacheInvalidate('framework/cache/data/GuzzleFileCache/public/books/prices');

补充说明:如果需要清除POST、PUT等其他请求方法的缓存,只需要调整宏里生成key前缀时传入的请求方法参数即可,默认场景下GET请求是最常用的缓存对象。

内容的提问来源于stack exchange,提问作者Federico Baù

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 00:06:35