diff --git a/README.md b/README.md index d86209b..7aef474 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,13 @@ php artisan workerman:serve stop - 失败重试:`WORKERMAN_MAX_TRIES`(默认 3),超时 `WORKERMAN_TIMEOUT`(默认 60s),重试耗尽进 `failed_jobs` 表 - 降级路径:`php artisan queue:work` 照常可用(同一队列) +## 前台缓存 + +- 匿名 GET 页面整页缓存(首页/列表/文章/归档/标签等),TTL 默认 300 秒(`PAGE_CACHE_TTL`),命中时零数据库查询 +- 文章浏览量由独立 `/track-view` 端点统计,不受整页缓存影响(实时) +- 后台「缓存管理」:清空页面/侧边栏/设置/全部缓存、重建编译缓存 +- 侧边栏数据与站点设置另有 1 小时缓存 + ## 对外 API - 交互式文档(OpenAPI/Swagger 风格):`GET /docs/api` diff --git a/app/Blog/Controllers/PostController.php b/app/Blog/Controllers/PostController.php index 65b94a7..1c685c5 100644 --- a/app/Blog/Controllers/PostController.php +++ b/app/Blog/Controllers/PostController.php @@ -25,7 +25,7 @@ class PostController extends Controller $this->ensureReadable($post); - $post->increment('views'); + // 浏览量由 /track-view 端点统计(页面整页缓存下控制器不执行,避免双计) $comments = $post->comments() ->where('status', 'published') diff --git a/app/Blog/Support/PageCacheMiddleware.php b/app/Blog/Support/PageCacheMiddleware.php new file mode 100644 index 0000000..70c45f8 --- /dev/null +++ b/app/Blog/Support/PageCacheMiddleware.php @@ -0,0 +1,99 @@ +shouldCache($request)) { + return $next($request); + } + + $key = $this->key($request); + $ttl = (int) config('blog.page_cache_ttl', 300); + + if ($cached = Cache::get($key)) { + return response($cached['body'], 200, [ + 'Content-Type' => $cached['type'], + 'Cache-Control' => 'public, max-age='.$ttl, + ]); + } + + $response = $next($request); + + if ($response->getStatusCode() === 200) { + Cache::put($key, [ + 'body' => $response->getContent(), + 'type' => $response->headers->get('Content-Type') ?: 'text/html; charset=utf-8', + ], $ttl); + self::rememberKey($key); + } + + return $response; + } + + public static function flush(): void + { + foreach ((array) Cache::get(self::TAG.'_keys', []) as $key) { + Cache::forget($key); + } + + Cache::forget(self::TAG.'_keys'); + } + + public static function count(): int + { + return count((array) Cache::get(self::TAG.'_keys', [])); + } + + private static function rememberKey(string $key): void + { + $keys = (array) Cache::get(self::TAG.'_keys', []); + $keys[$key] = true; + Cache::put(self::TAG.'_keys', $keys, now()->addDay()); + } + + private function shouldCache(Request $request): bool + { + if (! $request->isMethod('GET')) { + return false; + } + + if (auth()->check()) { + return false; + } + + $path = $request->path(); + + if (str_starts_with($path, 'admin') || str_starts_with($path, 'api') || str_starts_with($path, 'themes')) { + return false; + } + + // 动态端点 + if (str_starts_with($path, 'track-view')) { + return false; + } + + return true; + } + + private function key(Request $request): string + { + return self::TAG.':'.md5($request->fullUrl()); + } +} diff --git a/app/Filament/Pages/CacheManager.php b/app/Filament/Pages/CacheManager.php new file mode 100644 index 0000000..d12d2b7 --- /dev/null +++ b/app/Filament/Pages/CacheManager.php @@ -0,0 +1,64 @@ +title('前台页面缓存已清空')->success()->send(); + } + + public function flushSidebar(): void + { + foreach (['blog.sidebar.categories', 'blog.sidebar.recent_posts', 'blog.sidebar.recent_comments', 'blog.sidebar.hot_tags', 'blog.sidebar.links', 'blog.sidebar.stats'] as $key) { + Cache::forget($key); + } + Notification::make()->title('侧边栏缓存已清空')->success()->send(); + } + + public function flushSettings(): void + { + \App\Models\Setting::flushCache(); + Cache::forget('attach.legacy_ids'); + Notification::make()->title('设置缓存已清空')->success()->send(); + } + + public function flushAll(): void + { + PageCacheMiddleware::flush(); + \App\Models\Setting::flushCache(); + Cache::flush(); + Notification::make()->title('全部缓存已清空')->success()->send(); + } + + public function recompile(): void + { + Artisan::call('view:clear'); + Artisan::call('route:clear'); + Artisan::call('config:clear'); + Notification::make()->title('视图/路由/配置缓存已重建')->success()->send(); + } + + public function getPageCacheCountProperty(): int + { + return PageCacheMiddleware::count(); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 98fb428..91e43ff 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -29,6 +29,9 @@ class AppServiceProvider extends ServiceProvider $this->loadMigrationsFrom($path); } + // 前台页面缓存(匿名 GET,缓存含 SEO 注入后的最终 HTML) + app('router')->pushMiddlewareToGroup('web', \App\Blog\Support\PageCacheMiddleware::class); + // 后台侧边栏紧凑化(更窄的菜单项) Filament::serving(function () { Filament::registerRenderHook( @@ -37,6 +40,7 @@ class AppServiceProvider extends ServiceProvider