feat: 前台整页响应缓存(匿名 GET,浏览量走 track-view 独立端点)+ 后台缓存管理页 + 侧边栏 nav-groups 负 margin 归零

This commit is contained in:
ak
2026-08-11 21:53:09 +08:00
parent a5429b3ea4
commit 7622cd8e2a
21 changed files with 317 additions and 1 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ class PostController extends Controller
$this->ensureReadable($post);
$post->increment('views');
// 浏览量由 /track-view 端点统计(页面整页缓存下控制器不执行,避免双计)
$comments = $post->comments()
->where('status', 'published')
+99
View File
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace App\Blog\Support;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
/**
* 前台页面响应缓存:
* - 仅缓存匿名 GET 请求的 HTML 页面(首页/列表/文章/归档/标签等)
* - 登录用户、后台、API、动态端点不缓存
* - 浏览量走独立的 /track-view 端点,不受整页缓存影响
*/
class PageCacheMiddleware
{
public const TAG = 'pages';
public function handle(Request $request, \Closure $next): Response
{
if (! $this->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());
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Blog\Support\PageCacheMiddleware;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
class CacheManager extends Page
{
protected static \UnitEnum|string|null $navigationGroup = '管理';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bolt';
protected static ?string $navigationLabel = '缓存管理';
protected string $view = 'filament.pages.cache-manager';
public function flushPages(): void
{
PageCacheMiddleware::flush();
Notification::make()->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();
}
}
+4
View File
@@ -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
<style>
.fi-sidebar { padding-inline: 0.75rem; }
.fi-sidebar-nav { padding-inline: 0; }
.fi-sidebar-nav-groups { margin-inline: 0; }
.fi-sidebar-nav .fi-sidebar-item-button { padding-inline: 0.6rem; border-radius: 0.5rem; }
.fi-sidebar-nav .fi-sidebar-item-label { font-size: 0.82rem; }
.fi-sidebar-nav .fi-sidebar-group-label { font-size: 0.68rem; letter-spacing: 0.04em; }