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

165 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 从 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,不报错退出。