重新添加缺失的上传到数据库

我有一个网站,似乎数据库中的 uploads 表里有一批上传记录被删除了,但这些文件实际上仍存在于文件系统中。这导致指向这些文件的链接失效。我通过重新上传文件到新主题来创建数据库记录(当时我以为文件也丢失了,但 @angus 善意地指出我找不到它们的原因——总有一天我会问:为什么所有这些资源要同时拥有 sha1 base62 名称?),然后重新生成包含这些上传的帖子,方法如下:

def rebake_posts_with_uploads(topic_id, post_number)
  p = Post.find_by(topic_id: topic_id, post_number: post_number)
  exit unless p
  re = /upload:\/\/(.+?)\)/
  shas= p.raw.scan(re)

  shas.each do |sha|
    posts = Post.where("raw like '%#{sha[0]}%'")
    next unless posts
    puts "Found #{posts.count - 1} #{sha[0]}"
    posts.each do |post|
      next if post.id == p.id
      puts "rebake #{post.id}--#{BASE_URL}/t/-/#{post.topic_id}/#{post.post_number}"
      post.rebake!
    end
  end
end

既然文件存在于文件系统中但不在数据库中,我的新计划是执行类似以下操作:

for (all files in /shared/uploads/default/original/1x) do |file|
  unless file is in uploads table
     create upload record 
     for each post that includes that upload record
       rebake

这样看起来对吗?我正在查看 uploads.rake,但没发现任何似乎能实现此功能的现有代码。这有点像下面这段代码的反向操作:

不过,我不会执行 FileUtils.rm(file_path),而是会执行 Upload.create,我认为是这样。

如果这个方案看起来很愚蠢,或者有更好的解决方案,我很乐意在深入这个“兔子洞”之前听到您的建议。

谢谢。

我不知道这是怎么发生的。我原本希望将责任归咎于某个自定义插件,但恐怕并非如此。这可能与另一个讨论有关,其中有人提到:

是的,我们在这里的上传历史记录确实不太完整,这确实是我们的问题……@sam,你能推荐一个人给一些简短的建议吗?

这可行,但你必须小心,在本地进行测试……另外,请查看 2x / 3x 目录,那里到处都有上传内容。

我尝试了类似这样的代码:

def add_missing_files_to_uploads
  public_directory = Rails.root.join("public").to_s
  db = RailsMultisite::ConnectionManagement.current_db
  uploads_directory = File.join(public_directory, 'uploads', db).to_s
  # 上传文件和优化后的图片
  missing = 0
  matched = 0
  Dir.glob("#{uploads_directory}/**/*.pdf").each do |file_path|
    sha1 = Upload.generate_digest(file_path)
    url = file_path.split(public_directory, 2)[1]
    if (Upload.where(sha1: sha1).empty? &&
        Upload.where(url: url).empty?)
      puts "MISSING #{file_path}" if DEBUG
      missing += 1
    else
      matched += 1
    end
  end
  puts "MISS: #{missing}. Match #{matched}"
end

看起来是不是有点接近了?我的第一次测试似乎失败了,但我可能哪里弄错了。

你好 @pfaffman。我正在阅读这个话题,因为它看起来是解决我的问题的一个有希望的方案。你最终是否得到了满意的结果?

抱歉,我记不清了。

我和 Sam 都觉得这应该能行。

我之前通过创建一篇帖子,在其中插入所有图片的链接,然后重新构建所有包含 transparent.gif 的帖子,解决了一个模拟器问题。

这看起来是一个更为优雅的解决方案。

我建议先做一个仅数据库的备份,然后试试看。我目前有点像是在休假,但如果你有预算,我可以看看能做些什么。

我同样在 ams3 有一个重要客户;我还没把他们迁移到 AWS,但我想很快就要做了。我有一位曾在 DigitalOcean 工作的朋友,他推荐了 ams3,因为那是他们最好的数据中心,而且当时大部分资源都未被充分利用(那是很久以前的事了)。结果并不像我预期的那样顺利。

我想知道这是否是现今最好的方法?

我正试图恢复那些在 S3 迁移失败(使用了旧的 rake:s3_migrate)后存在于文件系统但不在数据库中的上传内容。

其实没有一个好的方法。:crying_cat:

但是,是的。想法仍然是那样。我不认为自那时起 Discourse 有任何变化。通常,我会手动做几项来确保它能按预期工作。另外,先备份一下,也许将站点设置为只读模式,这样如果你需要恢复备份,就不用丢弃你在摆弄东西时发表的任何帖子。

如果不了解比在论坛上容易传达的更多的信息,我就无法判断那是否真的是最好的方法,或者是否有更简单的方法。例如,你可能只需要对 URL 的路径使用 gsub。如果你有预算,可以联系我或在 Marketplace 询问。

好的,我通过 Claude 和大量的赞美解决了这个问题。我将分享我的做法,希望能帮助到遇到类似或相同问题的其他人。

我不确定这是不是最巧妙和最佳的使用方法,但它对我有效。

请小心并记住,我不是专家,而是一个不断学习的新手。

问题所在 (S3 → 本地文件系统)

从 AWS S3 迁移到本地文件系统后,很多图片显示为 transparent.png。文件总是在磁盘上,但 Discourse 无法解析它们。

根本原因是链条断裂:

  1. 带有 upload:// 短链接(base62 编码的 SHA1)的帖子
  2. 数据库中将 SHA1 映射到本地文件路径的 uploads 记录。
  3. 以其 SHA1 哈希命名的文件文件系统

迁移正确地将文件移动到了磁盘,但没有存在的 uploads 数据库记录。如果没有匹配的记录,Discourse 会回退到 transparent.png

解决方案 (创建记录并重新烘焙)

从孤立文件中创建缺失的上传记录:

dir = Rails.root.join("public", "uploads", "default", "original")
created = 0

Dir.glob(dir.join("**", "*")).select { |f| File.file?(f) }.each do |path|
  sha = File.basename(path, File.extname(path))
  next if Upload.find_by(sha1: sha)

  ext = File.extname(path).delete(".")
  relative = path.sub("#{Rails.root}/public", "")

  u = Upload.new
  u.sha1 = sha
  u.url = relative
  u.original_filename = File.basename(path)
  u.filesize = File.size(path)
  u.extension = ext
  u.user_id = -1
  u.save!(validate: false)

  created += 1
  puts "Created upload #{u.id}: #{sha}"
end

puts "Total created: #{created}"

重新烘焙引用了已恢复上传的帖子:

fixed_posts = 0

Upload.where(user_id: -1).find_each do |u|
  short = u.short_url
  next unless short

  Post.where("raw LIKE '%upload://%'").find_each do |p|
    urls = p.raw.scan(/upload:\/\/^[^\s\]\)]+/)
    urls.each do |url|
      decoded = Upload.sha1_from_short_url(url)
      if decoded == u.sha1
        p.rebake!
        fixed_posts += 1
        puts "Rebaked post #{p.id}"
        break
      end
    end
  end
end

puts "Total rebaked: #{fixed_posts}"

重新生成缺失的优化文件:

修复原始文件后,我们需要填充优化文件(1X、2X 等)。

rake uploads:regenerate_missing_optimized

安全回滚(以防万一)

所有创建的记录都使用 user_id: -1。要撤销:

Upload.where(user_id: -1).delete_all

delete_all 会跳过回调,因此文件系统中的文件不会被触动。

之前错误地使用了 destroy_all,它触发了将文件移至墓碑的回调。

我恢复了用于测试的单个文件,并重构了我的方法。