diff --git a/app/Blog/Controllers/ArchiveController.php b/app/Blog/Controllers/ArchiveController.php index b988edb..c4f3885 100644 --- a/app/Blog/Controllers/ArchiveController.php +++ b/app/Blog/Controllers/ArchiveController.php @@ -20,8 +20,17 @@ class ArchiveController extends Controller return theme_view('archives', compact('archives')); } - public function month(string $year, string $month) + /** + * sablog 格式:/archives-{YYYYMM}.shtml + */ + public function month(string $date) { + if (! preg_match('/^(\d{4})(\d{2})$/', $date, $m)) { + abort(404); + } + + [$year, $month] = [$m[1], $m[2]]; + $start = Carbon::create((int) $year, (int) $month, 1)->startOfMonth(); $end = (clone $start)->endOfMonth(); diff --git a/app/Blog/Controllers/LegacyController.php b/app/Blog/Controllers/LegacyController.php index 8335849..50cbbf7 100644 --- a/app/Blog/Controllers/LegacyController.php +++ b/app/Blog/Controllers/LegacyController.php @@ -78,7 +78,7 @@ class LegacyController extends Controller $tag = Tag::findFromString($name, 'post'); return $tag - ? redirect()->route('tags.show', $tag->slug ?: $tag->name, 301) + ? redirect()->route('tags.show', $tag->name, 301) : abort(404); } @@ -152,7 +152,7 @@ class LegacyController extends Controller app(\App\Blog\Support\PluginManager::class)->doAction('comment.created', $comment); - return redirect()->route('posts.show', $post->slug ?: $post->id) + return redirect()->route('posts.show', $post->id) ->with($status === 'published' ? 'success' : 'error', $status === 'published' ? '评论已发布' : '评论已提交,等待审核') ->withFragment('comments'); } @@ -162,7 +162,7 @@ class LegacyController extends Controller $post = Post::find($id); return $post - ? redirect()->route('posts.show', $post->slug ?: $post->id, 301) + ? redirect()->route('posts.show', $post->id, 301) : abort(404); } @@ -171,7 +171,7 @@ class LegacyController extends Controller $category = Category::find($cid); return $category - ? redirect()->route('category.show', $category->slug ?: $category->id, 301) + ? redirect()->route('category.show', $category->id, 301) : abort(404); } @@ -185,8 +185,8 @@ class LegacyController extends Controller $month = $m[2] ?? null; return $month - ? redirect()->route('archives.month', [$year, $month], 301) - : redirect()->route('archives.month', [$year, '01'], 301); + ? redirect()->route('archives.month', $year.$month, 301) + : redirect()->route('archives.month', $year.'01', 301); } private function tagList(Request $request) @@ -198,7 +198,7 @@ class LegacyController extends Controller $tag = $tagId ? Tag::find((int) $tagId) : Tag::find((int) $request->query('id')); return $tag - ? redirect()->route('tags.show', $tag->slug ?: $tag->name, 301) + ? redirect()->route('tags.show', $tag->name, 301) : abort(404); } diff --git a/app/Blog/Controllers/PostController.php b/app/Blog/Controllers/PostController.php index e1af2df..b1aa016 100644 --- a/app/Blog/Controllers/PostController.php +++ b/app/Blog/Controllers/PostController.php @@ -14,17 +14,22 @@ class PostController extends Controller { } - public function show(string $slug) + public function show(string $id, ?string $page = null) { $post = Post::query() - ->where('slug', $slug) - ->orWhere('id', (int) $slug) + ->where('id', (int) $id) + ->orWhere('slug', $id) ->first(); abort_unless($post && $post->status === 'published', 404); $this->ensureReadable($post); + // sablog 式 /show-{id}-{page}.shtml 的评论分页 + if ($page !== null) { + \Illuminate\Pagination\Paginator::currentPageResolver(fn () => max(1, (int) $page)); + } + // 浏览量由 /track-view 端点统计(页面整页缓存下控制器不执行,避免双计) $comments = $post->comments() diff --git a/app/Blog/Controllers/TagController.php b/app/Blog/Controllers/TagController.php index b29c374..28073d6 100644 --- a/app/Blog/Controllers/TagController.php +++ b/app/Blog/Controllers/TagController.php @@ -28,9 +28,12 @@ class TagController extends Controller return theme_view('tags', compact('tags')); } - public function show(string $slug) + /** + * sablog 格式:/tag/{name},按名称(而非 slug)解析 + */ + public function show(string $name) { - $tag = Tag::findFromString($slug, 'post'); + $tag = Tag::findFromString(urldecode($name), 'post'); abort_unless($tag, 404); diff --git a/app/Filament/Resources/Comments/Tables/CommentsTable.php b/app/Filament/Resources/Comments/Tables/CommentsTable.php index 99cbff6..65a9f82 100644 --- a/app/Filament/Resources/Comments/Tables/CommentsTable.php +++ b/app/Filament/Resources/Comments/Tables/CommentsTable.php @@ -32,7 +32,7 @@ class CommentsTable ->label('文章') ->limit(30) ->description(fn ($record) => $record->post ? \Illuminate\Support\Str::limit($record->post->excerpt_or_fallback, 50) : null) - ->url(fn ($record) => $record->post ? route('posts.show', $record->post->slug ?: $record->post->id) : null) + ->url(fn ($record) => $record->post ? route('posts.show', $record->post->id) : null) ->openUrlInNewTab(), TextColumn::make('status') ->label('状态') diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php index 2df9716..17fa675 100644 --- a/app/Http/Controllers/Api/ApiController.php +++ b/app/Http/Controllers/Api/ApiController.php @@ -110,7 +110,7 @@ class ApiController extends Controller 'views' => $post->views, 'comment_count' => $post->comment_count, 'published_at' => $post->published_at?->toIso8601String(), - 'url' => route('posts.show', $post->slug ?: $post->id), + 'url' => route('posts.show', $post->id), ]); } @@ -237,7 +237,7 @@ class ApiController extends Controller 'views' => $post->views, 'comment_count' => $post->comment_count, 'published_at' => $post->published_at?->toIso8601String(), - 'url' => route('posts.show', $post->slug ?: $post->id), + 'url' => route('posts.show', $post->id), ]; } } diff --git a/app/Models/Category.php b/app/Models/Category.php index 60db3ef..c67af9e 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -30,7 +30,7 @@ class Category extends Model implements HasMedia public function getUrlAttribute(): string { - return route('category.show', $this->slug ?? $this->id); + return route('category.show', $this->id); } public function registerMediaCollections(): void diff --git a/app/Models/Post.php b/app/Models/Post.php index bc9cee7..e9ad1d8 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -104,7 +104,7 @@ class Post extends Model implements HasMedia public function getUrlAttribute(): string { - return route('posts.show', $this->slug ?? $this->id); + return route('posts.show', $this->id); } /** diff --git a/plugins/neatstudio.blog-seo/src/Http/Middleware/SeoMiddleware.php b/plugins/neatstudio.blog-seo/src/Http/Middleware/SeoMiddleware.php index 68d1809..df456be 100644 --- a/plugins/neatstudio.blog-seo/src/Http/Middleware/SeoMiddleware.php +++ b/plugins/neatstudio.blog-seo/src/Http/Middleware/SeoMiddleware.php @@ -75,13 +75,13 @@ class SeoMiddleware '@context' => 'https://schema.org', '@type' => 'BlogPosting', 'headline' => $post->title, - 'url' => route('posts.show', $post->slug ?: $post->id), + 'url' => route('posts.show', $post->id), 'datePublished' => $post->published_at?->toIso8601String(), 'dateModified' => $post->updated_at?->toIso8601String(), 'author' => ['@type' => 'Person', 'name' => $post->author?->name ?? $siteName] + ($sameAs ? ['sameAs' => $sameAs] : []), 'publisher' => ['@type' => 'Organization', 'name' => $siteName, 'url' => url('/')] + ($sameAs ? ['sameAs' => $sameAs] : []), 'description' => Str::limit(strip_tags($post->excerpt_or_fallback), 200), - 'mainEntityOfPage' => route('posts.show', $post->slug ?: $post->id), + 'mainEntityOfPage' => route('posts.show', $post->id), ]; if ($cover = $post->getFirstMedia('cover')) { @@ -105,7 +105,7 @@ class SeoMiddleware '@type' => 'BreadcrumbList', 'itemListElement' => [ ['@type' => 'ListItem', 'position' => 1, 'name' => $siteName, 'item' => url('/')], - ['@type' => 'ListItem', 'position' => 2, 'name' => $category->name, 'item' => route('category.show', $category->slug ?: $category->id)], + ['@type' => 'ListItem', 'position' => 2, 'name' => $category->name, 'item' => route('category.show', $category->id)], ], ]; } diff --git a/plugins/neatstudio.blog-seo/src/Http/SeoController.php b/plugins/neatstudio.blog-seo/src/Http/SeoController.php index dba9731..27c90af 100644 --- a/plugins/neatstudio.blog-seo/src/Http/SeoController.php +++ b/plugins/neatstudio.blog-seo/src/Http/SeoController.php @@ -30,7 +30,7 @@ class SeoController $lines[] = ''; foreach ($posts as $post) { - $lines[] = '- ['.$post->title.']('.route('posts.show', $post->slug ?: $post->id).') — '.Str::limit(strip_tags($post->excerpt_or_fallback), 120); + $lines[] = '- ['.$post->title.']('.route('posts.show', $post->id).') — '.Str::limit(strip_tags($post->excerpt_or_fallback), 120); } return response(implode("\n", $lines)."\n") diff --git a/plugins/neatstudio.membership/src/Http/MembershipController.php b/plugins/neatstudio.membership/src/Http/MembershipController.php index 460f85b..ca87080 100644 --- a/plugins/neatstudio.membership/src/Http/MembershipController.php +++ b/plugins/neatstudio.membership/src/Http/MembershipController.php @@ -81,7 +81,7 @@ class MembershipController // 已能读付费内容(会员 / 作者 / 已解锁):直接查看文章,避免重复支付。 // 注意:付费块文章的整篇是公开的,不能用 canReadPost 判断(否则永远"已有权限") if ($this->service->canReadPaidContent($user, $post)) { - return redirect()->route('posts.show', $post->slug ?? $post->id); + return redirect()->route('posts.show', $post->id); } // 单篇付费解锁:创建一笔定向支付 diff --git a/resources/views/archives.blade.php b/resources/views/archives.blade.php index ce0e0b6..ad89bb1 100644 --- a/resources/views/archives.blade.php +++ b/resources/views/archives.blade.php @@ -11,11 +11,11 @@
@if($post->is_sticky)置顶@endif - {{ $post->title }} + {{ $post->title }}
@forelse($categories as $category)-
- {{ $category->name }}
+ {{ $category->name }}
({{ $category->post_count }})
@empty
@@ -18,7 +18,7 @@
最新文章
@forelse($recentPosts as $post) -- {{ $post->title }}
+ - {{ $post->title }}
@empty
- 暂无文章
@endforelse
@@ -30,7 +30,7 @@
@forelse($recentComments as $comment)-
-
+
{{ $comment->author_name }}:{{ Str::limit(strip_tags($comment->content), 30) }}
@@ -44,7 +44,7 @@
@forelse($hotTags as $tag)
- {{ $tag->name }}
+ {{ $tag->name }}
@empty
暂无标签
@endforelse
diff --git a/resources/views/show.blade.php b/resources/views/show.blade.php
index fa47e61..d6c7b67 100644
--- a/resources/views/show.blade.php
+++ b/resources/views/show.blade.php
@@ -40,7 +40,7 @@
@if($post->author)· {{ $post->author->name }}@endif
@if($post->category)
- · {{ $post->category->name }}
+ · {{ $post->category->name }}
@endif
· 阅读 {{ $post->views }}
@@ -53,7 +53,7 @@
@if($post->tags->isNotEmpty())
@foreach($post->tags as $tag)
- #{{ $tag->name }}
+ #{{ $tag->name }}
@endforeach
@endif
diff --git a/resources/views/tags.blade.php b/resources/views/tags.blade.php
index 0576dec..666d4b1 100644
--- a/resources/views/tags.blade.php
+++ b/resources/views/tags.blade.php
@@ -7,7 +7,7 @@
@forelse($tags as $tag)
-
+
{{ $tag->name }} ({{ $tag->posts_count }})
@empty
diff --git a/routes/web.php b/routes/web.php
index 5a6b0d3..bfa4167 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -31,38 +31,77 @@ Route::get('/', function () {
return app(HomeController::class)->index();
})->name('home');
-Route::get('/posts/{slug}.shtml', [PostController::class, 'show'])->where('slug', '[^/]+')->name('posts.show');
-Route::get('/posts/{slug}', fn (string $slug) => redirect()->route('posts.show', $slug, 301))->where('slug', '[^/]+');
+// sablog 首页分页格式:/index-{page}.shtml
+Route::get('/index-{page}.shtml', function (string $page) {
+ \Illuminate\Pagination\Paginator::currentPageResolver(fn () => max(1, (int) $page));
+
+ return app(HomeController::class)->index();
+})->whereNumber('page');
+
+Route::get('/posts/{slug}.shtml', function (string $slug) {
+ $post = \App\Models\Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
+
+ return $post ? redirect()->route('posts.show', $post->id, 301) : abort(404);
+})->where('slug', '[^/]+');
+Route::get('/posts/{slug}', function (string $slug) {
+ $post = \App\Models\Post::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
+
+ return $post ? redirect()->route('posts.show', $post->id, 301) : abort(404);
+})->where('slug', '[^/]+');
+
+// 文章正典:与 sablog 完全一致 /show-{id}.shtml(评论分页 /show-{id}-{page}.shtml)
+Route::get('/show-{id}.shtml', [PostController::class, 'show'])->whereNumber('id')->name('posts.show');
+Route::get('/show-{id}-{page}.shtml', [PostController::class, 'show'])->whereNumber('id')->whereNumber('page');
Route::post('/posts/{post}/comments', [CommentController::class, 'store'])->name('comments.store');
// 浏览量统计(独立端点,避开整页缓存,保证实时)
Route::get('/track-view/{post}', fn (\App\Models\Post $post) => response($post->increment('views') ? '' : '', 204))->name('track-view');
-Route::get('/category/{slug}.shtml', [CategoryController::class, 'show'])->where('slug', '[^/]+')->name('category.show');
-Route::get('/category/{slug}', fn (string $slug) => redirect()->route('category.show', $slug, 301))->where('slug', '[^/]+');
+Route::get('/category/{slug}.shtml', function (string $slug) {
+ $category = \App\Models\Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
+
+ return $category ? redirect()->route('category.show', $category->id, 301) : abort(404);
+})->where('slug', '[^/]+');
+Route::get('/category/{slug}', function (string $slug) {
+ $category = \App\Models\Category::query()->where('slug', $slug)->orWhere('id', (int) $slug)->first();
+
+ return $category ? redirect()->route('category.show', $category->id, 301) : abort(404);
+})->where('slug', '[^/]+');
+
+// 分类正典:/category-{cid}.shtml(分页 /category-{cid}-{page}.shtml)
+Route::get('/category-{cid}.shtml', [CategoryController::class, 'show'])->whereNumber('cid')->name('category.show');
+Route::get('/category-{cid}-{page}.shtml', [CategoryController::class, 'show'])->whereNumber('cid')->whereNumber('page');
Route::get('/archives.shtml', [ArchiveController::class, 'index'])->name('archives');
Route::get('/archives', fn () => redirect()->route('archives', [], 301));
-Route::get('/archives/{year}/{month}.shtml', [ArchiveController::class, 'month'])
- ->where(['year' => '\d{4}', 'month' => '\d{2}'])
- ->name('archives.month');
-Route::get('/archives/{year}/{month}', fn (string $year, string $month) => redirect()->route('archives.month', [$year, $month], 301))
+Route::get('/archives/{year}/{month}.shtml', fn (string $year, string $month) => redirect()->route('archives.month', $year.$month, 301))
->where(['year' => '\d{4}', 'month' => '\d{2}']);
+Route::get('/archives/{year}/{month}', fn (string $year, string $month) => redirect()->route('archives.month', $year.$month, 301))
+ ->where(['year' => '\d{4}', 'month' => '\d{2}']);
+// 按月归档正典:/archives-{YYYYMM}.shtml(分页 /archives-{YYYYMM}-{page}.shtml)
+Route::get('/archives-{date}.shtml', [ArchiveController::class, 'month'])->where('date', '\d{6}')->name('archives.month');
+Route::get('/archives-{date}-{page}.shtml', [ArchiveController::class, 'month'])->where(['date' => '\d{6}', 'page' => '\d+']);
-Route::get('/tags.shtml', [TagController::class, 'index'])->name('tags');
+// 标签正典:/tag/{name}(sablog 原版);列表 /tagslist.shtml
+Route::get('/tagslist.shtml', [TagController::class, 'index'])->name('tags');
+Route::get('/tagslist-{page}.shtml', [TagController::class, 'index'])->whereNumber('page');
+Route::get('/tag/{name}', [TagController::class, 'show'])->name('tags.show');
+Route::get('/tags.shtml', fn () => redirect()->route('tags', [], 301));
Route::get('/tags', fn () => redirect()->route('tags', [], 301));
-Route::get('/tags/{slug}.shtml', [TagController::class, 'show'])->where('slug', '[^/]+')->name('tags.show');
+Route::get('/tags/{slug}.shtml', fn (string $slug) => redirect()->route('tags.show', $slug, 301))->where('slug', '[^/]+');
Route::get('/tags/{slug}', fn (string $slug) => redirect()->route('tags.show', $slug, 301))->where('slug', '[^/]+');
Route::get('/search.shtml', [SearchController::class, 'index'])->name('search');
Route::get('/search', fn () => redirect()->route('search', [], 301));
+Route::get('/search-{page}.shtml', [SearchController::class, 'index'])->whereNumber('page');
Route::get('/links.shtml', [LinkController::class, 'index'])->name('links');
Route::get('/links', fn () => redirect()->route('links', [], 301));
Route::get('/comments.shtml', [CommentController::class, 'index'])->name('comments');
Route::get('/comments', fn () => redirect()->route('comments', [], 301));
+Route::get('/comments-{page}.shtml', [CommentController::class, 'index'])->whereNumber('page');
Route::get('/login.shtml', [AuthController::class, 'showLogin'])->name('login');
Route::get('/login', fn () => redirect()->route('login', [], 301));
@@ -109,29 +148,16 @@ Route::get('/themes/{theme}/assets/{path}', [ThemeAssetController::class, 'show'
Route::get('/show-{id}-{page}.html', [LegacyController::class, 'showRewritten'])
->where(['id' => '\d+', 'page' => '\d+']);
Route::get('/show-{id}.html', [LegacyController::class, 'showRewritten'])->where('id', '\d+');
-Route::get('/show-{id}-{page}.shtml', [LegacyController::class, 'showRewritten'])
- ->where(['id' => '\d+', 'page' => '\d+']);
-Route::get('/show-{id}.shtml', [LegacyController::class, 'showRewritten'])->where('id', '\d+');
Route::get('/category-{cid}-{page}.html', [LegacyController::class, 'categoryRewritten'])
->where(['cid' => '\d+', 'page' => '\d+']);
Route::get('/category-{cid}.html', [LegacyController::class, 'categoryRewritten'])->where('cid', '\d+');
-Route::get('/category-{cid}-{page}.shtml', [LegacyController::class, 'categoryRewritten'])
- ->where(['cid' => '\d+', 'page' => '\d+']);
-Route::get('/category-{cid}.shtml', [LegacyController::class, 'categoryRewritten'])->where('cid', '\d+');
Route::get('/archives-{date}-{page}.html', [LegacyController::class, 'archivesRewritten'])
->where(['date' => '\d+', 'page' => '\d+']);
Route::get('/archives-{date}.html', [LegacyController::class, 'archivesRewritten'])->where('date', '\d+');
-Route::get('/archives-{date}-{page}.shtml', [LegacyController::class, 'archivesRewritten'])
- ->where(['date' => '\d+', 'page' => '\d+']);
-Route::get('/archives-{date}.shtml', [LegacyController::class, 'archivesRewritten'])->where('date', '\d+');
Route::get('/tagslist-{page}.html', fn () => redirect()->route('tags', [], 301))->where('page', '\d+');
Route::get('/tagslist.html', fn () => redirect()->route('tags', [], 301));
-Route::get('/tagslist-{page}.shtml', fn () => redirect()->route('tags', [], 301))->where('page', '\d+');
-Route::get('/tagslist.shtml', fn () => redirect()->route('tags', [], 301));
Route::get('/comments-{page}.html', fn () => redirect()->route('comments', [], 301))->where('page', '\d+');
-Route::get('/comments-{page}.shtml', fn () => redirect()->route('comments', [], 301))->where('page', '\d+');
Route::get('/search-{page}.html', fn () => redirect()->route('search', [], 301))->where('page', '\d+');
-Route::get('/search-{page}.shtml', fn () => redirect()->route('search', [], 301))->where('page', '\d+');
Route::get('/reg.shtml', fn () => redirect()->route('register', [], 301));
Route::get('/tag/{name}', [LegacyController::class, 'tagByName']);
diff --git a/themes/sablog/views/partials/sidebar.blade.php b/themes/sablog/views/partials/sidebar.blade.php
index c6b157a..7cf17f8 100644
--- a/themes/sablog/views/partials/sidebar.blade.php
+++ b/themes/sablog/views/partials/sidebar.blade.php
@@ -11,7 +11,7 @@
@@ -52,7 +52,7 @@
@if($post->tags->isNotEmpty())
@foreach($post->tags as $tag)
- #{{ $tag->name }}
+ #{{ $tag->name }}
@endforeach
@endif
标签云
全部标签
分类
@forelse($categories as $category) -- {{ $category->name }} ({{ $category->post_count }})
+ - {{ $category->name }} ({{ $category->post_count }})
@empty
- 暂无分类
@endforelse
@@ -22,7 +22,7 @@
最新文章
@forelse($recentPosts as $post) -- {{ $post->title }}
+ - {{ $post->title }}
@empty
- 暂无文章
@endforelse
@@ -33,7 +33,7 @@
最新评论
@forelse($recentComments as $comment) -- {{ $comment->author_name }}:{{ Str::limit(strip_tags($comment->content), 20) }}
+ - {{ $comment->author_name }}:{{ Str::limit(strip_tags($comment->content), 20) }}
@empty
- 暂无评论
@endforelse
@@ -44,7 +44,7 @@
标签
@forelse($hotTags as $tag) - {{ $tag->name }} + {{ $tag->name }} @empty 暂无标签 @endforelse diff --git a/themes/sablog/views/show.blade.php b/themes/sablog/views/show.blade.php index def26bb..f714fe3 100644 --- a/themes/sablog/views/show.blade.php +++ b/themes/sablog/views/show.blade.php @@ -38,7 +38,7 @@ @if($post->author)· {{ $post->author->name }}@endif @if($post->category) - · {{ $post->category->name }} + · {{ $post->category->name }} @endif · 阅读 {{ $post->views }}