# Sablog 数据导入指南

把 SaBlog-X 1.6 老站数据完整迁入 LaraLog。**全部脚本化、可重复执行**，保留原始 ID，老 URL 自动 301 兼容（不产生 SEO 404）。

## 前置准备

1. 老库可访问：MySQL 8（或可导出的老 MySQL 5.x，先在 MySQL 8 建好导入目标）
2. 老附件目录：sablog 的 `attachments/`（在 Web 服务器上，含编号子目录）
3. 已按 `docs/install.md` 完成安装（`migrate --seed` 后导入）

> 建议：先在**测试环境**用 `--dry-run` 风格（见下文幂等说明）完整跑一遍，确认数量后再上生产。

## 一键导入

```bash
php artisan sablog:import \
  --host=127.0.0.1 --port=3306 \
  --database=sablog --username=root --password=老库密码 \
  --prefix=sablog_ \
  --attachments-dir=/var/www/old/attachments \
  --sync-attachments \
  --convert-markdown
```

### 参数说明

| 参数 | 默认 | 说明 |
|------|------|------|
| `--driver` | mysql | 老库驱动（测试可用 `sqlite`） |
| `--host` / `--port` | 127.0.0.1 / 3306 | 老库地址 |
| `--database` | （必填） | 老库名 |
| `--username` / `--password` | root / 空 | 老库账号 |
| `--prefix` | `sablog_` | 老表前缀（老站可能自定义过） |
| `--attachments-dir` | — | 老附件目录，配合 `--sync-attachments` 用 |
| `--sync-attachments` | 关 | 同步附件文件到新存储（本地中转或直接上 S3） |
| `--convert-markdown` | 关 | 导入时把 HTML 正文转为 Markdown |
| `--fresh` | 关 | 清空目标表后重新导入 |

## 数据映射

| 老表 | 新表 | 说明 |
|------|------|------|
| `users` | `users` | **ID 保留**；老 MD5 密码进 `legacy_md5`，登录时自动升级为 bcrypt |
| `articles` | `posts` | **articleid 保留为 id**；`[attach=xx]` 按内容格式处理（见下） |
| `categories` | `categories` | cid 保留；导入后自动重算文章数 |
| `comments` | `comments` | commentid 保留；visible 语义映射为状态 |
| `links` | `links` | linkid 保留 |
| `attachments` | medialibrary `media` | 文件 + `legacy_attachmentid` 映射（老 `[attach=xx]` 按此解析） |
| `settings` | `settings` | 键值映射为站点设置（可在后台「博客设置」继续改） |
| `tags` | spatie tags | 老 aids 文本解析为独立标签 + 关联 |

**不迁移**：trackback/引用通告（垃圾来源，接口已关闭）、搜索缓存表、验证码（由 AI 审核 + 蜜罐 + 频控取代）。

## 附件短代码的处理

| 老内容 | 导入后 |
|--------|--------|
| HTML 正文 `[attach=12]` | 原样保留，**渲染时按 legacy id 解析**为附件链接/图片 |
| Markdown 正文（--convert-markdown） | 自动转成 `{{attach:12}}` 令牌（`[]` 是 Markdown 保留字符） |

两种格式都能正常显示老附件，无需手工替换。

## 老 URL 兼容（301，无需 Nginx 配置）

- 伪静态：`/show-12-1.html` → 新文章页；`/category-3-1.html` → 新分类页；`/archives-202008-1.html` → 新归档
- 查询串：`/?action=show&id=12`、`/?action=search&keyword=x`、`/?action=tags&id=1` …全部映射
- 稳定路径：`/rss.xml`、`/sitemap.xml`、`/robots.txt` 保持原样
- 老入口：`attachment.php?id=` → 301 到 S3/本地媒体地址；`tburl.php`/`trackback.php` → 410

## 附件上 S3

```bash
# 导入后再把老附件批量上传（Multipart、幂等、可断点续传）
php artisan attachments:sync-s3 --legacy-dir=/var/www/old/attachments

# 个别文件校验失败需要强制重传
php artisan attachments:sync-s3 --legacy-dir=... --force
```

> 未配置 S3 时附件留在本地 public 磁盘，网站可正常展示；配好 S3 后再执行同步即可无缝切换。

## 内容转换（可选）

```bash
php artisan content:convert --all          # 全部 HTML 文章转 Markdown
php artisan content:convert 12             # 单篇
php artisan content:convert --all --dry-run # 先预览
```

## 幂等与重跑

- 导入按**原始 ID** `updateOrInsert`：重复执行不会产生重复数据，只会覆盖更新
- 需要完全重来：`--fresh` 清空目标表后导入
- `attachments:sync-s3` 自带去重：已上传的文件跳过（除非 `--force`）

## 导入后检查清单

- [ ] 文章总数、评论总数与老站一致（后台仪表盘 / `SELECT COUNT(*)`）
- [ ] 抽查老 URL：`/show-12-1.html` 301 到新文章页
- [ ] 抽查带 `[attach=xx]` 的文章：附件正常显示
- [ ] 老账号用**原密码**登录成功（密码自动升级为 bcrypt）
- [ ] 分类文章数、文章评论数正确（导入脚本已自动重算）
- [ ] RSS/Sitemap 正常输出
- [ ] 主题切换后前台布局正常

## 常见问题

| 现象 | 处理 |
|------|------|
| 报「找不到老表」 | 检查 `--prefix`（老站表前缀可能不是 `sablog_`）与老库名 |
| 附件显示不出来 | 确认 `--attachments-dir` 路径正确；看 `attachments:sync-s3` 输出报告 |
| 老密码登录失败 | 老库密码若是自定义加密（非 MD5），需要按 `User::verifyPassword` 扩展 |
| 中文乱码 | 老库导出时用 utf8mb4；`--driver=sqlite` 只用于测试 |
