我已经调试了这个问题,并找到了一个潜在的修复方案(在 Gemini 的帮助下)。
我在版本 3.4.5(我知道该版本可以正常工作)和最新版本上分别创建了这条消息(拖拽文件的内容并不重要):
然后我使用了另外两个账户下载了这两个文件,并刷新了页面。
对于“without-attachement”(无附件),我没有看到下载计数器(尽管当我使用源版本 v3.4.5 时它是有效的)。“with-attachment”(有附件)从未显示下载计数器,我只是为了“完整性”而添加了它。
当在调试器中执行以下代码时:
post = Post.last
TopicLink.counts_for(Guardian.new(post.user), post.topic, [post])
我得到了:
=> {8 =>
[{url: "/uploads/short-url/iMUBjeLNs0eRWZhdYnh24XGXY0f.funscript", clicks: 2, title: nil, internal: true, reflection: false},
{url: "/uploads/short-url/rgnPszmhmEizcRVBobULTkMP1UI.funscript", clicks: 0, title: nil, internal: true, reflection: false}]}
所以点击次数是存在的,但 UI 没有考虑它们,因为它们被用于防止用户查看指向私有/隐藏主题的链接点击次数的规则过滤掉了。问题在于,新规则没有考虑到内部链接可能是一个主题,也可能是一个内部文件。目前,任何 internal 且 not a topic 的链接都会被丢弃。
我通过修改 topic_link.rb 中的代码解决了这个问题:
def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
builder.where(<<\~SQL)
#{target_topic}.deleted_at IS NULL
AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)
AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
SQL
end
改为:
def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
builder.where(<<\~SQL)
#{target_topic}.deleted_at IS NULL
AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
AND (#{link}.internal = false OR #{link}.link_topic_id IS NULL OR #{target_topic}.id IS NOT NULL)
AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
SQL
end
Gemini 完整解释
你找到了确凿的证据!通过提供 topic_link.rb 的两个版本,现在非常清楚是什么破坏了 Discourse 最新版本中的此功能。
这个问题完全是由添加到 counts_for 数据库查询中的新安全过滤器引起的。
罪魁祸首:新的 apply_link_visibility_filters 方法
如果你查看新的 topic_link.rb 文件底部附近,你会发现这个新方法:
codeRuby
def self.apply_link_visibility_filters(builder, link:, target_topic:, target_posts:)
builder.where(<<~SQL)
#{target_topic}.deleted_at IS NULL
AND (#{target_topic}.id IS NULL OR #{target_topic}.visible = true)
AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL) <--- 这是 BUG
AND (#{link}.link_post_id IS NULL OR (#{link_posts}.id IS NOT NULL AND #{link_posts}.deleted_at IS NULL))
SQL
end
为什么这会破坏标准的文件上传
Discourse 开发者添加这条 SQL 规则是为了防止用户查看指向他们没有权限查看的私有/隐藏主题的链接的点击次数。然而,他们意外地波及了文件上传。
以下是 SQL 引擎如何评估你的 without-attachment.funscript 上传链接的:
- #{link}.internal = false:当保存上传文件时,Discourse 将其标记为 internal = true(因为文件托管在你的论坛上)。所以这评估为 FALSE。
- #{target_topic}.id IS NOT NULL:因为这是一个文件上传,而不是指向另一个论坛帖子的链接,所以它没有目标主题。主题 ID 为 NULL。所以这也评估为 FALSE。
- FALSE OR FALSE = FALSE。
由于这两个条件都失败,SQL 查询完全过滤掉了你的文件上传,并且 counts_for 方法向序列化器返回了一个空数组。
(注意:|attachment 仍然有效的原因是,前端通过一个完全独立的 API 端点/机制处理 .attachment 类以进行下载跟踪,从而完全绕过了 TopicLink!)
如何修复代码
要在你的本地环境中修复此 bug,你需要告诉 SQL 查询:“如果内部链接没有目标主题是可以的,只要它一开始就不应该有目标主题(例如文件上传)。"
在你的 Dev Container 中打开 app/models/topic_link.rb,找到 apply_link_visibility_filters 方法(大约在第 185 行),并将损坏的行更改为:
将此更改:
AND (#{link}.internal = false OR #{target_topic}.id IS NOT NULL)
改为:
AND (#{link}.internal = false OR #{link}.link_topic_id IS NULL OR #{target_topic}.id IS NOT NULL)
为什么这能修复它:
通过添加 #{link}.link_topic_id IS NULL,你是在告诉数据库允许纯粹是文件/上传的内部链接。它仍然会成功阻止那些 确实 有 link_topic_id 但 target_topic 缺失/隐藏的情况(这满足了 Discourse 开发者试图实现的安全要求)。
