环境
-
WP-Discourse 版本: 2.5.7
-
Discourse 版本: 3.5.0.beta5-dev
-
WordPress 版本: 6.9
-
托管: WP Engine (WordPress 多站点)
-
Discourse 实例: 自托管 / Discourse 托管 (forum.avweb.com)
摘要
从 WordPress 发布的内容成功在 Discourse 上创建了主题,但 discourse_permalink 存储在帖子元数据中时为空字符串。这导致所有后续的评论同步静默失败,因为 discourse-comment.php 中的 sync_comments() 在永久链接为空时会提前退出。Discourse 主题 ID 和 Webhook 同步标志写入正确——只是缺少永久链接。
我们在多站点网络中的一个站点上确定了 174 个受影响的帖子。该问题似乎始于 2025 年 12 月中旬左右。
重现步骤
-
发布一个配置为创建 Discourse 主题的 WordPress 帖子
-
主题在 Discourse 上成功创建
-
discourse_topic_id保存到帖子元数据中 ✓ -
wpdc_sync_post_comments设置为1(Webhook 正确触发) ✓ -
discourse_permalink保存为空字符串 ✗ -
discourse_comments_raw从未写入 (同步从未完成)
预期行为
成功创建主题后,discourse_permalink 应包含完整的 Discourse 主题 URL (例如,The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion)。
实际行为
discourse_permalink 作为元键存在,但其值为空。每次后续调用 sync_comments() 都会命中 lib/discourse-comment.php 第 209 行的此保护并提前返回:
if ( ! $discourse_permalink ) {
return 0;
}
由于同步从未完成,discourse_last_sync 从未写入,因此插件会在每次页面加载时重试——并且每次都失败。
诊断
我们通过以下代码路径追踪了问题:
-
DiscourseCommentFormatter::format()调用do_action('wpdc_sync_discourse_comments'),这会触发DiscourseComment::sync_comments() -
sync_comments()检查discourse_permalink—— 发现为空,返回 0 -
format()然后检查帖子自定义字段中的discourse_comments_raw—— 发现缺失,返回bad_response_html() -
评论从未显示在受影响的帖子中
主题创建流程正在写入 discourse_topic_id,但未能持久化永久链接。我们通过查询 Discourse API 在 /t/{topic_id}.json 处重建了正确的永久链接,并将其写回帖子元数据,从而解决了所有 174 个帖子的同步问题。
变通方法
我们编写了一个 WP-CLI 修复脚本,它:
-
查找
wpdc_sync_post_comments = 1但缺少discourse_comments_raw的帖子 -
从
/t/{topic_id}.json获取主题 slug -
重建并保存永久链接
-
从
/t/{slug}/{topic_id}/wordpress.json获取并保存评论
我们将其作为临时措施在计划的 cron 任务上运行。
调查期间发现的额外问题
1. 全局 MySQL 锁导致静默同步失败
文件: lib/discourse-comment.php,第 176 行
$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock', 0 ) got_it" );
sync_comments() 使用一个跨整个安装的所有帖子共享的全局 MySQL 锁 (discourse_lock)。超时设置为 0 (非阻塞),因此如果任何帖子当前正在同步,所有其他帖子都会静默跳过它们的同步——没有日志记录,没有重试。
在每天发布多个帖子的流量大的站点上,这会产生一个竞态条件,导致帖子持续丢失锁而永远无法同步。结合 10 分钟的同步周期,如果一个帖子在最初几次尝试中丢失了锁,并且由于永久链接为空的问题阻止了未来的同步,它可能会被永久卡住。
建议的修复: 使用每个帖子的锁:
$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock_{$post_id}', 0 ) got_it" );
以及相应的释放:
$wpdb->get_results( "SELECT RELEASE_LOCK( 'discourse_lock_{$post_id}' )" );
2. discourse_comments_raw 的双重转义
文件: lib/discourse-comment.php,第 222 行
update_post_meta( $post_id, 'discourse_comments_raw', esc_sql( $raw_body ) );
update_post_meta() 在内部使用 $wpdb->prepare(),因此用 esc_sql() 包装值会导致双重转义。在多个同步周期中,JSON 主体中的引号会积累转义字符 (\\" → \\\\\\" → \\\\\\\\\\\\\\"),直到 JSON 变得无法解析。
建议的修复:
update_post_meta( $post_id, 'discourse_comments_raw', $raw_body );
影响
这些问题会累积:空永久链接会阻止初始同步,全局锁会阻止恢复,而双重转义可能会破坏那些设法同步的帖子的数据。在我们的安装中,在我们确定根本原因之前,有 174 个帖子(大约 2 个月的内容)受到了影响。