我最近将我们的 Discourse 安装迁移到了一个子文件夹。执行此操作后,“显示完整帖子”按钮停止工作——点击以展开内容,但它没有加载完整的帖子。
我的 WP Discourse 配置中没有任何更改。
当直接在浏览器中访问嵌入式 URL 时,它返回 404 错误:
我最近将我们的 Discourse 安装迁移到了一个子文件夹。执行此操作后,“显示完整帖子”按钮停止工作——点击以展开内容,但它没有加载完整的帖子。
我的 WP Discourse 配置中没有任何更改。
当直接在浏览器中访问嵌入式 URL 时,它返回 404 错误:
这不相关,该路由只响应 application/json 内容类型。https://tecnoblog.net/comunidade/posts/483289/expand-embed.json 返回
"\"\u003cdiv\u003e\u003cdiv\u003e\u003c/div\u003e\u003c/div\u003e\\n\u003chr\u003e\\n\u003csmall\u003eEste é um tópico de discussão auxiliar para a entrada original em \u003ca href='https://tecnoblog.net/noticias/paramount-oferece-us-108-bilhoes-em-dinheiro-para-tomar-warner-da-netflix'\u003ehttps://tecnoblog.net/noticias/paramount-oferece-us-108-bilhoes-em-dinheiro-para-tomar-warner-da-netflix\u003c/a\u003e\u003c/small\u003e\\n\""
\u003cdiv\u003e\u003cdiv\u003e\u003c/div\u003e\u003c/div\u003e 应该是内容。
你是否也更改了博客网址?
onebox 的显示对我来说也很奇怪,我期望它会有一个缓存的截断内容,所以我假设 body.present? 在上面的条件中是 false。
你能进入 Rails 控制台并检查 TopicEmbed.where(topic_id: 157441).pick(:embed_url) 是否向你显示了正确的博客内容网址吗?
你能在 https://tecnoblog.net/comunidade/logs 上发现任何相关的错误吗?
哦,好的!
它返回帖子的网址:
discourse(prod) => TopicEmbed.where(topic_id: 157441).pick(:embed_url)
=> “``https://tecnoblog.net/noticias/paramount-oferece-us-108-bilhoes-em-dinheiro-para-tomar-warner-da-netflix”
我不认为日志中有任何相关的错误。
没有!博客网址一直都是 tecnoblog.net
还值得一提的是,服务器的 IP 在 CF(Cloudflare)的防火墙中被绕过了:
我曾多次遇到并调试过此类问题,这很复杂,请耐心配合。
请运行以下脚本并在此处分享输出
# 用您正在调试的主题 ID 或 URL 替换此处内容
topic_id = 386983
# 1. 检查 TopicEmbed 是否存在及其内容
te = TopicEmbed.find_by(topic_id: topic_id)
puts "TopicEmbed 存在: #{te.present?}"
puts "嵌入 URL: #{te&.embed_url}"
puts "内容缓存存在: #{te&.embed_content_cache.present?}"
puts "内容缓存长度: #{te&.embed_content_cache&.length || 0}"
puts "内容 SHA1: #{te&.content_sha1}"
# 2. 检查实际缓存的内容(前 500 个字符)
puts "\n--- 缓存内容预览 ---"
puts te&.embed_content_cache&.truncate(500)
# 3. 尝试从远程 URL 获取
if te&.embed_url.present?
puts "\n--- 尝试远程获取 ---"
begin
response = TopicEmbed.find_remote(te.embed_url)
puts "远程获取成功: #{response.present?}"
puts "远程正文存在: #{response&.body.present?}"
puts "远程正文长度: #{response&.body&.length || 0}"
puts "远程标题: #{response&.title}"
puts "远程正文: #{response&.body&.truncate(500)}"
rescue => e
puts "远程获取失败: #{e.message}"
end
end
# 4. 检查 expanded_for 会返回什么
if te.present?
puts "\n--- 测试 expanded_for ---"
post = Post.find(te.post_id)
# 清除缓存以强制重新获取
Discourse.cache.delete("embed-topic:#{topic_id}")
begin
expanded = TopicEmbed.expanded_for(post)
puts "展开内容存在: #{expanded.present?}"
puts "展开内容长度: #{expanded&.length || 0}"
rescue => e
puts "expanded_for 失败: #{e.message}"
end
end
# 5. 检查相关设置
puts "\n--- 站点设置 ---"
puts "embed_truncate: #{SiteSetting.embed_truncate}"
puts "allowed_embed_selectors: #{SiteSetting.allowed_embed_selectors}"
puts "blocked_embed_selectors: #{SiteSetting.blocked_embed_selectors}"
这将显示为什么 https://tecnoblog.net/comunidade/t/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento/157462?u=falco 出现故障
discourse(prod)> # Replace with the topic ID or URL you’re debugging
discourse(prod)> topic_id = 386983
discourse(prod)>
discourse(prod)> # 1. Check if TopicEmbed exists and its content
discourse(prod)> te = TopicEmbed.find_by(topic_id: topic_id)
discourse(prod)> puts “TopicEmbed exists: #{te.present?}”
discourse(prod)> puts “Embed URL: #{te&.embed_url}”
discourse(prod)> puts “Content cache present: #{te&.embed_content_cache.present?}”
discourse(prod)> puts “Content cache length: #{te&.embed_content_cache&.length || 0}”
discourse(prod)> puts “Content SHA1: #{te&.content_sha1}”
discourse(prod)>
discourse(prod)> # 2. Check the actual cached content (first 500 chars)
discourse(prod)> puts “\n— Cached content preview —”
discourse(prod)> puts te&.embed_content_cache&.truncate(500)
discourse(prod)>
discourse(prod)> # 3. Try fetching from the remote URL
discourse(prod)* if te&.embed_url.present?
discourse(prod)* puts “\n— Attempting remote fetch —”
discourse(prod)* begin
discourse(prod)* response = TopicEmbed.find_remote(te.embed_url)
discourse(prod)* puts “Remote fetch success: #{response.present?}”
discourse(prod)* puts “Remote body present: #{response&.body.present?}”
discourse(prod)* puts “Remote body length: #{response&.body&.length || 0}”
discourse(prod)* puts “Remote title: #{response&.title}”
discourse(prod)* puts “Remote body: #{response&.body&.truncate(500)}”
discourse(prod)* rescue => e
discourse(prod)* puts “Remote fetch FAILED: #{e.message}”
discourse(prod)* end
discourse(prod)> end
discourse(prod)>
discourse(prod)> # 4. Check what expanded_for would return
discourse(prod)* if te.present?
discourse(prod)* puts “\n— Testing expanded_for —”
discourse(prod)* post = Post.find(te.post_id)
discourse(prod)*
discourse(prod)* # Clear cache to force fresh fetch
discourse(prod)* Discourse.cache.delete(“embed-topic:#{topic_id}”)
discourse(prod)*
discourse(prod)* begin
discourse(prod)* expanded = TopicEmbed.expanded_for(post)
discourse(prod)* puts “Expanded content present: #{expanded.present?}”
discourse(prod)* puts “Expanded content length: #{expanded&.length || 0}”
discourse(prod)* rescue => e
discourse(prod)* puts “expanded_for FAILED: #{e.message}”
discourse(prod)* end
discourse(prod)> end
discourse(prod)>
discourse(prod)> # 5. Check relevant settings
discourse(prod)> puts “\n— Site Settings —”
discourse(prod)> puts “embed_truncate: #{SiteSetting.embed_truncate}”
discourse(prod)> puts “allowed_embed_selectors: #{SiteSetting.allowed_embed_selectors}”
discourse(prod)> puts “blocked_embed_selectors: #{SiteSetting.blocked_embed_selectors}”
TopicEmbed exists: false
Embed URL:
Content cache present: false
Content cache length: 0
Content SHA1:
— Cached content preview —
— Site Settings —
embed_truncate: true
allowed_embed_selectors:
blocked_embed_selectors:
=> nil
discourse(prod)>
![]()
您确定这是正确的主题 ID 吗?https://tecnoblog.net/comunidade/t/-/386983 链接到 404 页面。
原来如此。我链接的主题实际上是 157462。
我的错!
这是正确帖子ID的结果
TopicEmbed 存在: true
Embed URL: https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento
Content cache 存在: true
Content cache 长度: 22
Content SHA1:
— 缓存内容预览 —
<div><div></div></div>
— 尝试远程获取 —
Remote fetch 成功: true
Remote body 存在: true
Remote body 长度: 22
Remote title:
Remote body:
— 测试 expanded_for —
Expanded content 存在: true
Expanded content 长度: 309
— 站点设置 —
embed_truncate: true
allowed_embed_selectors:
blocked_embed_selectors:
=> nil
您的 Cloudflare 绕过成功了吗?看起来 https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento 的正文只有 22 个字符,没有标题标签。
是的!来自 discourse 服务器的所有请求都已绕过:

我注意到嵌入的 URL 末尾没有斜杠。所有 URL 都应该有尾随斜杠。

所以也许 discourse 没有遵循重定向?
但为什么它保存的 URL 没有尾随斜杠呢?
这很容易测试,请尝试
url = "https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento/"
response = TopicEmbed.find_remote(url)
puts "远程获取成功: #{response.present?}"
puts "远程正文存在: #{response&.body.present?}"
puts "远程正文长度: #{response&.body&.length || 0}"
puts "远程标题: #{response&.title}"
puts "远程正文: #{response&.body&.truncate(500)}"
我认为它有效:
discourse(prod)> url = “https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento/”
discourse(prod)> response = TopicEmbed.find_remote(url)
discourse(prod)> puts “Remote fetch success: #{response.present?}”
discourse(prod)> puts “Remote body present: #{response&.body.present?}”
discourse(prod)> puts “Remote body length: #{response&.body&.length || 0}”
discourse(prod)> puts “Remote title: #{response&.title}”
discourse(prod)> puts “Remote body: #{response&.body&.truncate(500)}”
Remote fetch success: true
Remote body present: true
Remote body length: 3776
Remote title: Governo renova app da CNH para baratear obtenção do documento • Tecnoblog
Remote body:
<figure><img src="https://files.tecnoblog.net/wp-content/uploads/2025/12/cnh-brasil-app-1060x596.jpg">
<figcaption>Aplicativo CNH do Brasil (imagem: Emerson Alecrim/Tecnoblog)</figcaption></figure>
</div>
<details>
Resumo
<div><ul>
<li>App CNH do Brasil substitui CDT e passa a oferecer recursos para obtenção da CNH, em especial, aulas teóricas gratuitas;</li>
<li>Aulas práticas continuam obrigatórias, mas a carga horária mínima foi reduzida de ...
=> nil
在没有尾部斜杠的情况下,如下所示:
discourse(prod)> url = “https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento”
discourse(prod)> response = TopicEmbed.find_remote(url)
discourse(prod)> puts “Remote fetch success: #{response.present?}”
discourse(prod)> puts “Remote body present: #{response&.body.present?}”
discourse(prod)> puts “Remote body length: #{response&.body&.length || 0}”
discourse(prod)> puts “Remote title: #{response&.title}”
discourse(prod)> puts “Remote body: #{response&.body&.truncate(500)}”
Remote fetch success: true
Remote body present: true
Remote body length: 22
Remote title:
Remote body:
=> nil
在帖子帖文的 slug 发生变化时,旧帖子中也会出现同样的错误。
例如,在这篇帖子中,以前使用的网址是:
https://tecnoblog.net/486925/o-que-e-pirataria-digital/
现在,它已更改为:
这似乎是主要问题。当使用 在另一个网站上通过 Javascript 嵌入 Discourse 评论 时,您可以通过一个参数来控制它,修复起来非常简单。
我不熟悉 WP-Discourse 是如何确定这个的,它应该使用帖子的规范(canonical)地址,但我不确定。@angus 有什么想法吗?
有没有办法强制 Discourse 更新一个分类下的所有嵌入式网址,并跟随它到最终目的地?
我打算在嵌入式 Discourse(您一直在测试的完整嵌入)准备好投入生产时迁移到它。但是,如果嵌入式网址不匹配,那么它可能会为每个帖子创建新主题并丢失评论……
运行
te = TopicEmbed.find_by(topic_id: 157462)
te.embed_url = te.embed_url + "/"
te.save
它奏效了!
但是有没有针对这种情况的修复方法?
Gemini 建议了以下代码:
# 配置
CATEGORY_SLUG = 'tb'
category = Category.find_by(slug: CATEGORY_SLUG)
unless category
puts "错误:未找到类别 '#{CATEGORY_SLUG}'。"
exit
end
puts "正在开始对类别 '#{category.name}' 中的 URL 进行完整扫描..."
puts "这可能需要一些时间,具体取决于主题的数量和您网站的响应速度..."
count_updated = 0
count_errors = 0
count_ok = 0
Topic.where(category_id: category.id).find_each do |topic|
current_url = topic.custom_fields["embed_url"]
# 如果没有 embed_url,则跳过
next unless current_url.present?
begin
# 发送 GET 请求并跟随重定向
response = Faraday.get(current_url)
final_url = response.env.url.to_s
# 如果请求成功 (200 OK)
if response.status == 200
# 检查最终 URL 是否与数据库中保存的 URL 不同
# 比较忽略细微差异,但这里我们比较精确的字符串
if final_url != current_url
puts "\n[更新] 主题 ##{topic.id}:"
puts " 从: #{current_url}"
puts " 到: #{final_url}"
topic.custom_fields["embed_url"] = final_url
topic.save_custom_fields(true)
count_updated += 1
else
# print "." # 取消注释以查看视觉进度(点点)
count_ok += 1
end
else
puts "\n[HTTP 错误 #{response.status}] 主题 ##{topic.id} - URL: #{current_url}"
count_errors += 1
end
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
puts "\n[连接失败] 主题 ##{topic.id} - URL: #{current_url} - #{e.message}"
count_errors += 1
rescue StandardError => e
puts "\n[一般错误] 主题 ##{topic.id} - #{e.message}"
count_errors += 1
end
# 可选:短暂暂停,以免使您的 WordPress 服务器超载
# sleep 0.1
end
puts "\n\n最终摘要:"
puts "------------------------------------------------"
puts "已检查的主题 (正常): #{count_ok}"
puts "已更新的主题: #{count_updated}"
puts "发现的错误: #{count_errors}"
puts "------------------------------------------------"
终于取得了一些进展 ![]()
这样的脚本是个好主意,只是在运行它之前先进行备份。
哪怕只是这个小表的备份也会很有帮助。
好的!我会在团队完成轮班后再试着运行它。
嘿,各位,我看到尾部斜杠又出问题了 ![]()
[尾部斜杠是] 主要问题。当使用 通过 Javascript 在其他网站上嵌入 Discourse 评论 时,您可以通过一个参数来控制它,修复起来非常简单。
仅供参考,Discourse 中的所有主题嵌入都会从 embed_url 中删除尾部斜杠;请参阅 TopicEmbed.normalize_url。由于一个涉及 javascript 嵌入和 WP Discourse 嵌入交叉的独立案例,我们在这两种嵌入方法中标准化了这种处理。请参阅 Apply TopicEmbed url normalisation to embed urls inserted in the PostCreator by angusmcleod · Pull Request #30641 · discourse/discourse · GitHub
@Thiago_Mobilon 在这次迁移过程中,您是否也更新了您的 Discourse?有可能是我们将 embed_url 标准化处理应用于 WP Discourse 嵌入的结果,这是在迁移到子文件夹安装的同时发生的。您目前运行的是哪个版本的 Discourse?(如果您知道的话,迁移前运行的是哪个版本?)
顺便说一句,当我在最新版本的 Discourse 上在本地运行以下两个命令时,我得到了相同的结果,即文章的 HTML 正文
# 带尾部斜杠
TopicEmbed.find_remote("https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento/")
# 不带尾部斜杠
TopicEmbed.find_remote("https://tecnoblog.net/noticias/governo-renova-app-da-cnh-para-baratear-obtencao-do-documento")
# 产生相同的结果
您是否可能在 WordPress 端做了一些更改?
您好,Angus!
不,这些是不同的问题。尾部斜杠在我们迁移到子文件夹时就开始出现,但也有很多几年前的旧 URL,它们的 slug 现在不同了。
我不得不重建安装,所以是的,我认为这个新的标准可能是原因。
我建议的修复此问题的方法是:Discourse 能否至少跟踪一两个重定向来检索数据?这将解决尾部斜杠问题,并在将来可能发生 URL 更改时使网站更安全。
此外,这样做更安全,因为没有必要运行脚本来更新旧主题,那也可能对数据库造成一些损害。