M2: 主题系统(sablog经典+modern简约)+ 前台全部页面 + 老 URL 301 兼容 + markdown 双份导入

This commit is contained in:
ak
2026-08-11 17:58:29 +08:00
parent f16bb75538
commit 6214976a88
66 changed files with 2763 additions and 10 deletions
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use App\Models\Post;
use Illuminate\Console\Command;
use League\HTMLToMarkdown\HtmlConverter;
class ConvertContent extends Command
{
protected $signature = 'content:convert
{--all : 转换全部 html 文章}
{id? : 单篇文章 ID}
{--dry-run : 只预览不写库}';
protected $description = '把文章内容从 HTML 转换为 Markdown(老 sablog 数据迁移用)';
public function handle(): int
{
$query = Post::query()->where('content_format', 'html');
if ($id = $this->argument('id')) {
$query->where('id', $id);
} elseif (! $this->option('all')) {
$this->error('请指定文章 ID 或使用 --all');
return self::FAILURE;
}
$converter = new HtmlConverter([
'strip_tags' => false,
'hard_break' => true,
]);
$renderer = app(\App\Blog\Services\PostContentRenderer::class);
$posts = $query->get();
if ($posts->isEmpty()) {
$this->info('没有需要转换的 HTML 文章');
return self::SUCCESS;
}
$bar = $this->output->createProgressBar($posts->count());
$bar->start();
foreach ($posts as $post) {
// [attach=xx] 是 markdown 保留字符,先换成令牌,渲染时再解析
$markdown = $converter->convert($renderer->toMarkdownSafeTokens($post->content));
if ($this->option('dry-run')) {
$this->newLine();
$this->line("[{$post->id}] {$post->title}");
$this->line(mb_substr($markdown, 0, 200));
$bar->advance();
continue;
}
$post->content = $markdown;
$post->content_format = 'markdown';
$post->save();
$bar->advance();
}
$bar->finish();
$this->newLine();
$this->info('转换完成:'.($this->option('dry-run') ? '预览模式,未写库' : $posts->count().' 篇已转换为 Markdown'));
return self::SUCCESS;
}
}
+41 -6
View File
@@ -26,6 +26,7 @@ class SablogImport extends Command
{--prefix=sablog_ : 老表前缀}
{--attachments-dir= : attachments 目录(用于同步文件,可选)}
{--sync-attachments : 同步附件文件到新存储}
{--convert-markdown : 导入后把 HTML 内容转为 Markdown[attach=xx] 转成 {{attach:xx}} 令牌,渲染时解析)}
{--fresh : 清空目标表后重新导入}';
protected $description = '从 SaBlog-X 老库脚本迁移数据到 laralog';
@@ -63,6 +64,10 @@ class SablogImport extends Command
$this->importLinks();
$this->importAttachments();
if ($this->option('convert-markdown')) {
$this->convertToMarkdown();
}
$this->syncCounters();
$this->newLine();
@@ -157,7 +162,7 @@ class SablogImport extends Command
$count = 0;
foreach ($this->rows('categories') as $row) {
$slug = SlugGenerator::make($row['name'], 'categories', 'slug', ignoreId: (int) $row['cid'], fallback: 'category-'.$row['cid']);
Category::query()->updateOrCreate(['id' => $row['cid']], [
DB::table('categories')->updateOrInsert(['id' => (int) $row['cid']], [
'name' => $row['name'],
'slug' => $slug,
'display_order' => (int) $row['displayorder'],
@@ -182,7 +187,7 @@ class SablogImport extends Command
$count = 0;
foreach ($this->rows('users') as $row) {
$email = $this->legacyEmail($row['username'], $count);
$user = User::query()->updateOrCreate(['id' => $row['userid']], [
DB::table('users')->updateOrInsert(['id' => (int) $row['userid']], [
'name' => $row['username'],
'email' => $email,
'password' => null,
@@ -197,8 +202,9 @@ class SablogImport extends Command
'updated_at' => now(),
]);
$user = User::find((int) $row['userid']);
$role = $groupMap[(int) $row['groupid']] ?? 'member';
if (! $user->hasRole($role)) {
if ($user && ! $user->hasRole($role)) {
$user->assignRole($role);
}
$count++;
@@ -222,13 +228,14 @@ class SablogImport extends Command
$status = (int) $row['visible'] === 1 ? 'published' : 'draft';
$dateline = date('Y-m-d H:i:s', (int) $row['dateline']);
Post::query()->updateOrCreate(['id' => $row['articleid']], [
DB::table('posts')->updateOrInsert(['id' => (int) $row['articleid']], [
'category_id' => (int) $row['cid'] ?: null,
'user_id' => (int) $row['uid'] ?: null,
'title' => $row['title'],
'slug' => $slug,
'excerpt' => $row['description'] ?: null,
'content' => $row['content'],
'content_format' => 'html',
'keywords' => $row['keywords'] ?: null,
'status' => $status,
'is_sticky' => (bool) $row['stick'],
@@ -248,6 +255,7 @@ class SablogImport extends Command
private function importTags(): void
{
$legacyMap = [];
$count = 0;
foreach ($this->rows('tags') as $row) {
$tagName = trim($row['tag']);
@@ -262,6 +270,8 @@ class SablogImport extends Command
$tag->save();
}
$legacyMap[(int) $row['tagid']] = $tag->id;
foreach (explode(',', (string) $row['aids']) as $aid) {
$post = Post::find((int) trim($aid));
if ($post && ! $post->tags()->where('tags.id', $tag->id)->exists()) {
@@ -271,6 +281,10 @@ class SablogImport extends Command
$count++;
}
if ($legacyMap) {
Setting::set('legacy_tags', $legacyMap);
}
$this->report['tags'] = $count;
}
@@ -278,7 +292,7 @@ class SablogImport extends Command
{
$count = 0;
foreach ($this->rows('comments') as $row) {
Comment::query()->updateOrCreate(['id' => $row['commentid']], [
DB::table('comments')->updateOrInsert(['id' => (int) $row['commentid']], [
'post_id' => (int) $row['articleid'],
'author_name' => $row['author'] ?: '匿名',
'author_url' => $row['url'] ?: null,
@@ -297,7 +311,7 @@ class SablogImport extends Command
{
$count = 0;
foreach ($this->rows('links') as $row) {
Link::query()->updateOrCreate(['id' => $row['linkid']], [
DB::table('links')->updateOrInsert(['id' => (int) $row['linkid']], [
'name' => $row['name'],
'url' => $row['url'],
'note' => $row['note'] ?: null,
@@ -312,6 +326,27 @@ class SablogImport extends Command
$this->report['links'] = $count;
}
private function convertToMarkdown(): void
{
$this->info('转换文章内容为 Markdown ...');
$converter = new \League\HTMLToMarkdown\HtmlConverter([
'strip_tags' => false,
'hard_break' => true,
]);
$renderer = app(\App\Blog\Services\PostContentRenderer::class);
$count = 0;
foreach (Post::query()->where('content_format', 'html')->cursor() as $post) {
$post->content = $converter->convert($renderer->toMarkdownSafeTokens($post->content));
$post->content_format = 'markdown';
$post->save();
$count++;
}
$this->report['markdown_converted'] = $count;
}
private function importAttachments(): void
{
if (! $this->db->query('SHOW TABLES LIKE "'.$this->prefix.'attachments"')->fetchColumn()) {