# 从 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;新后台是 `/admin`(Filament)。 ## 导入前 1. 目标库已 `php artisan migrate`(含插件 migration;`AppServiceProvider` 会加载 `plugins/*/database/migrations`)。 2. **不要先跑完整 `db:seed`。** 演示种子占用 `users.id=1`、`categories.id=1`、`articles.id=1/2`,导入按同 ID **upsert 覆盖**,还可能留下演示站多出来的行。 3. 角色表是空的:导入**不会**写 Spatie 角色。导入后要自己建 `admin` 并赋给旧站管理员(见文末)。 4. 配好附件盘:开发 `ATTACHMENTS_DRIVER=local`;生产用 S3/R2 等(见 [deploy.md](./deploy.md))。 5. 源库账号建议只读。 ## 源库与附件目录 `.env`(连接名默认 `sablog`,见 `config/database.php`): ```env 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: ```env SABLOG_DB_CHARSET=gbk ``` 命令里再加 `--encoding=gbk` 或 `--encoding=auto`(非合法 UTF-8 时按 GBK 转)。 附件目录必须是**绝对路径**,且相对路径与表字段 `filepath` 一致。例如库里是 `2020/01/foo.jpg`,则文件应在: ```text /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,可重跑)。 ## 命令 先连通、只计数(不写目标库、不上传): ```bash php artisan sablog:import --mode=raw --dry-run ``` 正式导入: ```bash 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`: ```bash php artisan sablog:import --mode=raw --attachments=/正确/路径 --retry-failed ``` 已成功(`synced_at` 有值)的附件默认不再上传。 ## 导入后:登录与后台 旧站密码是 **MD5**。导入后: - 前台 `/login.shtml` 用**用户名 + 旧密码**可登录;校验成功后升级为 bcrypt,并清空 `password_legacy`。 - Filament `/admin` 用**邮箱**登录。导入用户 `email` 为空,且**没有** `admin` 角色,所以要补一步: ```bash php artisan tinker ``` ```php 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 设置可单独灌,不必跑演示博客种子: ```bash 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,不报错退出。