⭐ 推荐:社区规则条款 V1.0

迁移CMS时如何保留原站的文章修订版本?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-28 15:56 ·2 浏览 ·3 回复
内容摘要

迁移CMS保留修订版本须直接迁移修订数据表并重建父子关系;同构整表搬运、异构先建ID映射,目标站不支持则降级为存档或差异记录。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2669.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

liulian
liulian 初级会员萌新站长 1楼 2026-10-28 16:02

补充几个实操里最容易翻车的点——尤其是“同构直接整表搬运”这条路,光搬 wp_posts 是不够的。

一、修订行的附属数据必须一起走。 修订本身也有 postmeta(自定义字段的历史值存在修订行下),另外 wp_term_relationships.object_id、wp_postmeta 里的 _thumbnail_id 全都是写死的文章 ID。异构迁移时如果只重写了修订行的 post_parent,而分类、标签、特色图片没跟着做映射,结果就是文章搬过去了、归类全空。建议映射表统一用一张 id_map(old_id, new_id) 临时表,最后所有引用文章 ID 的字段一次性 JOIN UPDATE 完,比一条条改省事也安全。

二、异构匹配别只靠标题。 同标题重发、草稿标题重复太常见了,实测优先级应该是:旧站 slug(post_name)唯一匹配 → post_title + post_date 组合匹配 → 剩下的打日志人工核。匹配失败率超过 5% 就别硬灌了,先回头查源数据。

三、目标站不支持修订的降级方案,我更推荐“存档表 + 只读页”:建一张 revision_archive(article_id, rev_time, content, diff_html),前台只读页展示、不进搜索索引、不进评论流,比塞隐藏版块干净得多,后期想清理也只是一张表。

四、迁移完必做两步校验:先用 wp post list --post_type=revision --format=count 比对新旧条数;再随机抽 20 篇,比对“最新修订内容”与“当前正文”是否一致——很多站当前正文其实就是最新修订的复制,这里对不上说明写入顺序错了(先灌正文后灌修订会导致时间戳倒挂)。

最后提一句:WP 站点如果修订条数爆炸,导入前 define('WP_POST_REVISIONS', 10) 配合 wp post delete $(wp post list --post_type=revision --format=ids) 先裁一遍,百万行能砍掉一大半导入时间。Drupal 那边注意 node_revision 和 node_field_revision 是两张表,只搬前者会出现正文历史在、字段历史不在的半吊子状态。

juming
juming 作者初级会员 #3748 2楼 2026-10-28 16:21
liulian:补充几个实操里最容易翻车的点——尤其是“同构直接整表搬运”这条路,光搬 `wp_posts` 是不够的。 **一、修订行的附属数据必须一起走。** 修订本身也…

id_map 的思路完全对,但有两处得补一刀。

一是 define('WP_POST_REVISIONS', 10) 这行最容易误用:它只在走 wp_insert_post() 的写入路径里生效,wp db import 或者裸 INSERT 是绕过这层的,常量设了也删不掉一行。所以裁剪必须在导出前在源站执行,顺序反了就是白等一轮导入。真正想控量,可以先 wp post delete 裁完再 wp db export。

二是「引用文章 ID 的字段」其实散在三个不同的列名上,写 JOIN UPDATE 时别只盯着 post_parent:wp_postmeta 里是 post_id,wp_term_relationships 和 wp_commentmeta 里是 object_id,wp_posts 里才是 post_parent。_thumbnail_id 的 meta_value 是纯数字,走映射很安全;但 _wp_attachment_metadata 和 Gutenberg 块注释里的引用嵌的是序列化结构,必须 --precise 处理,裸 REPLACE 会把长度字段写坏。

校验建议在条数之外再加一层内容指纹,新旧站跑同一条语句比对,比抽 20 篇更靠谱:

SELECT post_parent, COUNT(*) AS c,
       MD5(GROUP_CONCAT(MD5(post_content) ORDER BY post_date)) AS fp
FROM wp_posts WHERE post_type='revision' GROUP BY post_parent;

另外轮询时间戳倒挂那点,根因往往是 INSERT 没显式带上 post_date / post_modified,让列默认值兜了底,导数据时把这两个字段写死就没这问题。

chinaz
chinaz 初级会员萌新站长 #3751 3楼 2026-10-28 16:30
juming:id_map 的思路完全对,但有两处得补一刀。 一是 `define('WP_POST_REVISIONS', 10)` 这行最容易误用:它只在走 `wp_i…

两条都站得住,尤其是第一条——常量管不住裸 INSERT 这一点,我见过两次有人设了 WP_POST_REVISIONS 就直接导库,白等几个小时才发现一行没少。

指纹校验那条我再补一个隐藏问题:GROUP_CONCAT 默认 group_concat_max_len 只有 1024 字节,一篇有几十个修订、每个快照几 KB,聚合串早就被静默截断了,新旧站算出来的 fp 反而“看起来完全一致”,比不校验更危险。跑之前先 SET SESSION group_concat_max_len = 100000000;(同会话内有效,写成迁移脚本时别丢这句),或者换成 BIT_XOR(CRC32(MD5(post_content))) 这类定长聚合,天然不受长度限制。

时间戳再往下钻一层:只写 post_date / post_modified 不够,post_date_gmt 和 post_modified_gmt 必须一起写死。这俩字段一旦让默认值兜底,前台按 GMT 排序、RSS 输出、date_query 全错,而且不会报任何错,通常要等用户反馈“文章时间差八小时”才被发现。

裁剪那步也顺带提一句:wp post delete 删修订行不会连带清 wp_postmeta 里挂在这些修订 ID 下的记录,裁完补一条 DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT ID FROM wp_posts),不然瘦身效果要打对折。

另外一个常见问题:导完后跑一遍 wp post list --post_type=revision,确认 post_status 都是 inherit,有些导出工具会把它写成 publish,结果修订全跑到前台列表里去了。