【问题标题】:Rails 4.2.x - zero size uploaded filesRails 4.2.x - 零大小上传文件
【发布时间】:2016-02-07 04:39:39
【问题描述】:

我在视图中显示上传的图像时遇到问题。 IMO 由于/public/uploads 中每个上传文件的大小为零。

目前正在尝试掌握上传和提供文件没有任何 ruby​​/rails gem

为此,我运行了一个带有基本脚手架的原始应用程序,仅用于测试上传/保存周期:

schema.rb:

ActiveRecord::Schema.define(version: 20151104043709) do

  create_table "uploads", force: :cascade do |t|
    t.string   "filename"
    t.string   "content_type"
    t.binary   "content"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
  end

end

UploadsController#create:

  def create
    @upload = Upload.new(upload_params)
    data = params[:upload][:file]

    File.open(Rails.root.join('public', 'uploads', data.original_filename), 'wb') do |f|
      f.write(data.read)
    end

    respond_to do |format|
      if @upload.save
        format.html { redirect_to @upload, notice: 'Upload was successfully created.' }
        format.json { render :show, status: :created, location: @upload }
      else
        format.html { render :new }
        format.json { render json: @upload.errors, status: :unprocessable_entity }
      end
    end
  end

模型上传.rb:

class Upload < ActiveRecord::Base
    include ActionView::Helpers::NumberHelper
    attr_accessor :upload

    def file=(upload_data)
        self.filename = upload_data.original_filename
        self.content_type = upload_data.content_type
        self.content = upload_data.read
    end

    def filename=(new_filename)
        write_attribute("filename", sanitize_filename(new_filename))
    end

private

    def sanitize_filename(filename)
        just_filename = File.basename(filename)
        just_filename.gsub(/[^\w\.\-]/, '_')
    end
end

上传#显示视图:

<p id="notice"><%= notice %></p>

<p>
  <strong>Filename:</strong>
  <%= @upload.filename %>
</p>

<p>
  <strong>Content type:</strong>
  <%= @upload.content_type %>
</p>

<p>
  <%= image_tag "/public/uploads/#{@upload.filename}" %>
</p>

<%= link_to 'Edit', edit_upload_path(@upload) %> |
<%= link_to 'Back', uploads_path %> | 
<%= link_to 'Download', download_path(id: @upload) %>

问题:不顺利的是所有文件在/public/uploads dir 中都获得了 0 字节。

并像这样查看渲染:

索引视图:

并且无法加载图像示例:

并证明所有文件都是零字节:

注意: 二进制文件应该可以正常工作,因为可以使用send_data 下载每个文件:

  def download
    send_data(@upload.content,
              filename: @upload.filename,
              content_type: @upload.content_type,
              )
  end

+

============================================ 问题:应用程序有什么问题以及如何解决它以显示图像或图像?提前感谢您的帮助!

【问题讨论】:

  • 视图中 image_tag 的 HTML 标记:&lt;img src="/public/uploads/61.JPG" alt="61" /&gt;
  • 使用其中一颗宝石。严重地。回形针。载波。去做吧。

标签: ruby-on-rails image file-upload


【解决方案1】:

试试这个,我认为这应该会有所帮助。

original_filename =  data.original_filename.to_s
tmp = data.tempfile
file = File.join("public/uploads", original_filename)
FileUtils.cp tmp.path, file

【讨论】:

  • 当用 def file=(uploaded_data) 中的原始代码替换您的建议代码时 -> 实例已创建,但文件名、content_type 和二进制文件中的数据为空白。我说得对吗,必须放入:def file=(upload_data) new_filename = upload_data.original_filename.to_s tmp = upload_data.tempfile file = File.join("public", "uploads", new_filename) FileUtils.cp tmp.path, file end
  • 另外,当尝试 dwnl 这个文件时,它会在 zise 中显示 0 字节,所以在实例创建时二进制也不行
  • 你需要在你的控制器中而不是在模型中替换它。将其替换为 File.open(Rails.root.join('public', 'uploads', data.original_filename), 'wb') do |f| f.write(data.read) end
  • 我还添加了我之前的代码:self.filename = new_filenameself.content_type = upload_data.content_typeself.content = upload_data.read 现在实例已创建,文件名、MIME、二进制都可以,/public/uploads 中没有零字节文件.. 但是..视图中没有图像显示
  • 检查存储在数据库中的文件名和public/uploads上传文件的名称
【解决方案2】:

您似乎在混合使用两种不同的文件存储方法:

  1. 在数据库中存储为二进制 Blob(例如:数据库中的 content 列)
  2. 将文件保存到文件系统(例如:/public/uploads/

您的实现同时做了一点点:您肯定将文件保存为uploads 表的content 列中的二进制文件,并且您正在将一些东西保存到文件系统.你需要选择一个或另一个。

如果您想使用方法 2(文件系统),则需要 更多的工作来完成它。如果这是您真正想要做的,请告诉我,我可以用解决方案编辑我的答案。

但是,方法 1(数据库) 已经差不多了。我们只需要以稍微不同的方式将各个部分组合在一起。

方法 1:在数据库中存储二进制 Blob

您提到send_data 正在为您工作——这正是我们将用来解决您的问题的方法。

首先,给UploadsController添加一个方法

def show_image
  @upload = Upload.find(params[:id])
  send_data @upload.content, type: @upload.content_type, disposition: 'inline'
end

接下来,添加对应的路由到routes.rb

get 'show_image', to: 'uploads#show_image'

最后,在你看来,/uploads/show.html.erb,把你的 image_tag 换成这个

<%= image_tag url_for(controller: "uploads", action: "show_image", id: @upload.id) %>

然后我们开始了。这应该足以让它工作。

我们基本上定义了一个控制器操作来从数据库中获取二进制 blob,然后使用 send_data'inline' 处置以正确的 MIME 类型将文件返回到您的视图,以便您的 link_tag 在您的view 知道如何处理它。

您会注意到,您可以删除 UploadsControllercreate 方法中的几乎所有内容。您只需要标准样板代码:

def create
  @upload = Upload.new(upload_params) # <-- All you need

  respond_to do |format|
  ...
end

您也可以删除 /public/uploads 目录,因为您没有使用它。该文件使用这种方法完全存储在数据库中,不会出现在您的文件系统中。

希望这对您有所帮助!让我知道这是否适合您。


注意方法2(文件系统)

我猜您这样做是为了获得学习体验,这很棒,但如果您曾经尝试将上传的文件存储在“真实”应用程序的文件系统中,那么您真的 应该使用 gem 来处理它。 Paperclip 让事情变得非常简单,即使在最终将文件存储在 S3 等其他地方的生产应用程序中也是一个有用的工具,因此学习它绝对不是浪费。

方法 2 - 将文件存储在文件系统中

好的,事实证明您的文件系统工作代码实际上非常接近工作。主要问题是在upload.rbfile=(upload_data) 方法中,你调用了upload_data.read。然后,在UploadsController#create 中,您调用f.write(data.read)。这里要注意的重要一点是dataupload_data 是完全相同的文件流。为什么这很重要?

嗯,Ruby(以及大多数其他文件读取方法)中的read 的事情是隐藏的可变状态可以跟踪文件流内容中的当前“位置”。 (这很有用,因为大多数文件读取是分批完成的,例如 foreach 一次读取一行文件。)所以 file.read 真正 所做的是它“读取其余部分文件”。如果您的“位置”在开头,那么“文件的其余部分”就是整个文件。但如果你已经完成了一半,那么“剩下的”只是下半场。因此,“当前位置”在完成read-ing 后总是出现在文件的最后,这意味着(除非我们“倒带”)任何未来对read 的调用都将返回空字符串,因为没有更多的“文件的其余部分”。示例:

myfile = File.open(some_path_to_file, 'r')
myfile.read
  # => "lorem ipsum..." # (file contents from beginning)
myfile.read
  # => ""  # (empty string)
myfile.rewind
  # => 0 (resets 'position' to position 0, the beginning)
myfile.read
  # => "lorem ipsum..." # (file contents from beginning)

(好的,如果您想在阅读答案之前尝试一下,那么这些信息足以解决问题。如果没有,或者当您准备好时,请继续阅读......)

解决方案

upload.rb中,进行如下修改 file=(upload_data)

def file=(upload_data)
  self.filename = upload_data.original_filename
  self.content_type = upload_data.content_type
  self.content = upload_data.read # <-- Either Delete this line, OR
  upload_data.rewind              # <-- Add this line
end

显然,在一个真实的应用程序中,我们会删除 self.content 行,因为我们正在尝试使用文件系统实现方法 2,但由于这是一个测试应用程序,您完全可以同时工作.为此,只需在最后添加 upload_data.rewind 行。

所以现在create 方法应该成功地将文件添加到您的/public/uploads 目录(它们不再为空),因此请检查以确保其正常工作。接下来,我们只需要确保正确设置视图。

show.rb 中,图像标签应如下所示

<%= image_tag "/uploads/#{@upload.filename}" %>

你有/public/uploads/... 而不是/uploads/...。出于安全原因,image_tag 只能查看 /public 内部,因此您不必在路径中指定“/public”——它会自动添加。

而且,我们完成了!请让我知道这对你有没有用。

更多

还有一些你可能应该做的事情。比如稍微修改create这个动作:

File.open(Rails.root.join('public', 'uploads', data.original_filename), 'wb') do |f|
  f.write(data.read)
  f.close # <-- add this line. You should always explicitly close file streams after you open them.
end

#### something else here

另外,不要像我们那样对路径进行硬编码,而是将路径存储在数据库中的 file_path 列中,然后我们可以将 @upload.file_path 传递给我们的 image_tag

rails g migration AddFilePathToUploads file_path:string
rake db:migrate

然后确保我们的create 操作在保存之前手动设置路径。所以插入以下内容而不是“这里的其他东西”

@upload.file_path = ['/uploads', data.original_filename].join '/'

然后更改show.rb中的图片标签

<%= image_tag @upload.file_path %>

好的,现在我们完成了。我只是想再次提出建议以检查 Paperclip 或 CarrierWave gem,因为它们是您可能用于任何实际项目的东西。

我希望你学到了一些东西,这对你有帮助。我也玩得很开心。当你让它工作时告诉我!

干杯

【讨论】:

  • 天啊!!!有用!!!方法1有效!!! :) 我花了整整 3 天的时间在互联网上搜索解决方案,现在我明白了! @Sherlock_HJ 非常感谢!!
  • 不客气!是的,我将通过文件系统更新我的答案。不过我可能要到明天才有时间。
  • 好的@Trakaitisguesthouse,方法 2 已启动!告诉我进展如何。
  • 方法 1 & 2 完美运行。谢谢!当你指出这些要点时,所有这些要点似乎都很熟悉,但是当我在没有手册或监督的情况下独立实施新事物时,不知何故我设法忘记了它们并感到沮丧。希望是由于缺乏实践:)
  • 不错。是的,这一切都伴随着练习。在这一点上我给你的建议是,在你开始实现某事之前,拿出一张纸或打开一个笔记文件,写出你算法的高级“伪代码”。然后,您可以越来越具体地了解每个部分,直到您拥有真正的代码。这样可以防止您犯错误,例如将文件系统与数据库表混为一谈。无论如何,祝你好运,编码愉快!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 2020-11-27
  • 2011-12-06
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多