Files
gouki 3cec4c5e18
CI / PHPUnit (PHP 8.3) (push) Failing after 4s
CI / PHPUnit (PHP 8.2) (push) Failing after 1m9s
CI / Deploy (manual gate) (push) Skipped
wip: article AI polish, category SEO fields, cover generator, membership plan seeder
2026-09-07 18:48:37 +00:00

6.2 KiB
Raw Permalink Blame History

从 sablog 导入

把 SaBlog-X 的内容迁到 LaraBlog保留文章 / 分类 ID 与内容向 URL,附件进对象存储(或本地磁盘),源库只读、不改源站文件。

命令:php artisan sablog:import
实现:app/Console/Commands/SablogImportCommand.php
回归夹具:tests/fixtures/sablog/php artisan test --filter=SablogImportTest

迁什么、不迁什么

源表(默认前缀 sablog_ 结果
users 保留 userid;密码写入 password_legacy(无盐 MD5);随机 bcrypt 占位;邮箱为空
categories 保留 cid
articles 保留 articleid、阅读数、置顶、可见、关闭评论、阅读密码
comments visible=1 → 已通过,否则待审;不触发评论插件/AI 审核
tags + aids 标签及文章关联
links / stylevars 友情链接、站点片段
attachments 元数据 + 按 --attachments 目录上传;正文仍存引用,不写死 CDN
trackbacks / trackbacklog / searchindex / sessions 跳过,只在报告里计数

不导入旧 PHP 后台、WAP、Trackback。旧 /admin/*.php 在新站返回 410;新后台是 /adminFilament)。

导入前

  1. 目标库已 php artisan migrate(含插件 migrationAppServiceProvider 会加载 plugins/*/database/migrations)。
  2. 不要先跑完整 db:seed 演示种子占用 users.id=1categories.id=1articles.id=1/2,导入按同 ID upsert 覆盖,还可能留下演示站多出来的行。
  3. 角色表是空的:导入不会写 Spatie 角色。导入后要自己建 admin 并赋给旧站管理员(见文末)。
  4. 配好附件盘:开发 ATTACHMENTS_DRIVER=local;生产用 S3/R2 等(见 deploy.md)。
  5. 源库账号建议只读。

源库与附件目录

.env(连接名默认 sablog,见 config/database.php):

SABLOG_DB_DRIVER=mysql
SABLOG_DB_HOST=127.0.0.1
SABLOG_DB_PORT=3306
SABLOG_DB_DATABASE=sablog
SABLOG_DB_USERNAME=readonly
SABLOG_DB_PASSWORD=
SABLOG_DB_CHARSET=utf8mb4

旧库若是 GBK

SABLOG_DB_CHARSET=gbk

命令里再加 --encoding=gbk--encoding=auto(非合法 UTF-8 时按 GBK 转)。

附件目录必须是绝对路径,且相对路径与表字段 filepath 一致。例如库里是 2020/01/foo.jpg,则文件应在:

/data/sablog/attachments/2020/01/foo.jpg

缩略图字段 thumb_filepath 同样按该根目录拼接;存在则一并上传。

两种正文模式

--mode 库内正文 content_format 附件引用
raw(推荐先跑) 保持 HTML html 原样 [attach=123]
markdown HTML → Markdown markdown 改成 attach:123(避免 [] 被 Markdown 吃掉)

两种模式都不把对象存储 URL 写进正文。前台渲染再变成 /attachment.php?id=123

不确定旧文 HTML 质量时先 raw,确认站点可访问后再决定是否用 markdown 重导(同 ID upsert,可重跑)。

命令

先连通、只计数(不写目标库、不上传):

php artisan sablog:import --mode=raw --dry-run

正式导入:

php artisan sablog:import \
  --mode=raw \
  --connection=sablog \
  --prefix=sablog_ \
  --attachments=/绝对路径/sablog/attachments \
  --disk=attachments \
  --encoding=auto
参数 默认 说明
--connection sablog Laravel 连接名
--prefix sablog_ 源表前缀
--attachments 本地附件根目录;不传则附件记为 missing
--mode raw raw | markdown
--encoding auto utf8 | gbk | auto
--disk attachments 目标 disk
--retry-failed 已有 synced_at 的附件默认跳过;打开则重试失败/缺失
--dry-run 只读源库并打印计数

成功后打印一张表:users / categories / articles / comments / tags / links / stylevars / attachments_ok / attachments_missing / attachments_failed / 跳过的 trackback 等。源库与源文件不会被修改。

整次导入包在一个事务里(附件上传在事务内;失败会 warn 并继续记 missing/failed 行)。缺文件不会中断整次导入。

附件补传

第一次没带 --attachments、或路径不对导致 attachments_missing

php artisan sablog:import --mode=raw --attachments=/正确/路径 --retry-failed

已成功(synced_at 有值)的附件默认不再上传。

导入后:登录与后台

旧站密码是 MD5。导入后:

  • 前台 /login.shtml用户名 + 旧密码可登录;校验成功后升级为 bcrypt,并清空 password_legacy
  • Filament /admin邮箱登录。导入用户 email 为空,且没有 admin 角色,所以要补一步:
php artisan tinker
use App\Models\User;
use Spatie\Permission\Models\Role;

Role::findOrCreate('admin');
Role::findOrCreate('editor');
Role::findOrCreate('member');

$user = User::query()->where('username', '旧站管理员用户名')->first();
$user->forceFill(['email' => 'you@example.com'])->save();
$user->assignRole('admin');

然后打开 /admin,邮箱 + 旧站密码(或升级后的同一密码)。

AI 设置可单独灌,不必跑演示博客种子:

php artisan db:seed --class=Database\\Seeders\\AiSettingsSeeder

导入后检查

  • 首页、/show-{旧文章id}.shtml/category-{旧分类id}.shtml
  • 正文图是否经 /attachment.php?id= 能打开
  • 评论作者、友情链接、站点片段
  • attachments_missing / attachments_failed 是否可接受
  • 阅读密码文章:前台仍走密码墙(与单篇付费、会员可见互斥,见后台校验)

注意

  • 幂等:按原 ID upsert,可重复执行;附件成功过的默认跳过。
  • ID 对齐是为了 SEO:不要在导入后再批量改文章 ID。
  • 导入用户没有邮箱、没有角色;前台读者用用户名登录即可。
  • 插件付费/会员数据不在 sablog 里,导入后如需付费墙,在新后台按篇配置。
  • 夹具库仅供测试,不是完整 sablog 结构说明;以你线上表为准。缺表则该项计数为 0,不报错退出。