feat: 社交账号设置(微博/GitHub/X/Facebook/知乎/邮箱)+ JSON-LD sameAs(Person/Organization/WebSite)+ og:image/og:locale/article 时间 + twitter 完整卡片

This commit is contained in:
ak
2026-08-11 23:33:30 +08:00
parent 3ff766d980
commit ef8ec75e83
12 changed files with 65 additions and 5 deletions
@@ -60,6 +60,7 @@ class SeoMiddleware
private function structuredData(Request $request): array
{
$siteName = blog_setting('site_name', config('blog.name'));
$sameAs = $this->socialLinks();
$route = $request->route();
@@ -70,18 +71,24 @@ class SeoMiddleware
});
if ($post && $post->status === 'published') {
return [
$data = [
'@context' => 'https://schema.org',
'@type' => 'BlogPosting',
'headline' => $post->title,
'url' => route('posts.show', $post->slug ?: $post->id),
'datePublished' => $post->published_at?->toIso8601String(),
'dateModified' => $post->updated_at?->toIso8601String(),
'author' => ['@type' => 'Person', 'name' => $post->author?->name ?? $siteName],
'publisher' => ['@type' => 'Organization', 'name' => $siteName],
'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),
];
if ($cover = $post->getFirstMedia('cover')) {
$data['image'] = [$cover->getUrl('card') ?: $cover->getUrl()];
}
return $data;
}
}
@@ -90,26 +97,52 @@ class SeoMiddleware
'@type' => 'WebSite',
'name' => $siteName,
'url' => url('/'),
];
] + ($sameAs ? ['sameAs' => $sameAs] : []);
}
/**
* 后台配置的社交账号 -> schema sameAsFacebook / GitHub / Twitter 等平台识别)。
*/
private function socialLinks(): array
{
$links = [];
foreach (['social_weibo', 'social_github', 'social_twitter', 'social_facebook', 'social_zhihu'] as $key) {
if ($url = blog_setting($key, '')) {
$links[] = $url;
}
}
return array_values(array_unique($links));
}
private function ogTags(Request $request, array $data): string
{
$siteName = blog_setting('site_name', config('blog.name'));
$isArticle = ($data['@type'] ?? '') === 'BlogPosting';
$image = $data['image'][0] ?? null;
$tags = [
'<meta property="og:site_name" content="'.e($siteName).'">',
'<meta property="og:locale" content="zh_CN">',
'<meta property="og:type" content="'.($isArticle ? 'article' : 'website').'">',
'<meta property="og:title" content="'.e($data['headline'] ?? $siteName).'">',
'<meta property="og:url" content="'.e(url()->current()).'">',
'<meta property="og:description" content="'.e($data['description'] ?? blog_setting('site_description', '')).'">',
'<meta name="twitter:card" content="summary_large_image">',
'<meta name="twitter:card" content="'.($image ? 'summary_large_image' : 'summary').'">',
'<meta name="twitter:title" content="'.e($data['headline'] ?? $siteName).'">',
'<meta name="twitter:description" content="'.e($data['description'] ?? blog_setting('site_description', '')).'">',
];
if ($image) {
$tags[] = '<meta property="og:image" content="'.e($image).'">';
$tags[] = '<meta property="og:image:alt" content="'.e($data['headline'] ?? $siteName).'">';
$tags[] = '<meta name="twitter:image" content="'.e($image).'">';
}
if ($isArticle) {
$tags[] = '<meta property="article:published_time" content="'.e($data['datePublished'] ?? '').'">';
$tags[] = '<meta property="article:modified_time" content="'.e($data['dateModified'] ?? '').'">';
}
return implode("\n", $tags);