feat: 前台整页响应缓存(匿名 GET,浏览量走 track-view 独立端点)+ 后台缓存管理页 + 侧边栏 nav-groups 负 margin 归零
This commit is contained in:
@@ -25,7 +25,7 @@ class PostController extends Controller
|
||||
|
||||
$this->ensureReadable($post);
|
||||
|
||||
$post->increment('views');
|
||||
// 浏览量由 /track-view 端点统计(页面整页缓存下控制器不执行,避免双计)
|
||||
|
||||
$comments = $post->comments()
|
||||
->where('status', 'published')
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user