Image validation
圖片上傳可以運作了,不過目前並沒有針對上傳的檔案做任何限制,這可能會導致問題,例如使用者嘗試上傳檔案很大的檔案或是不合法的檔案類型。為了彌補這個缺陷,我們要在 server 和 client 端(例如瀏覽器)對圖片大小和格式增加驗證功能。
圖片格式的限制可以在 CarrierWave 的上傳程序中設定。我們要限制能使用的圖片副檔名(PNG、GIF、JPG、JPEG):
app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
storage :file
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Add a white list of extensions which are allowed to be uploaded.
def extension_white_list
%w(jpg jpeg gif png)
end
end
圖片大小的限制在 Micropost model 裡面設定。和前面的 model 驗證不同,Rails 沒有為檔案大小提供現成的驗證方法。所以我們要自訂驗證方法,把這個方法命名為 picture_size,注意,調用自定義的驗證時,使用的是 validate,而不是 validates:
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
validate :picture_size
private
# Validates the size of an uploaded picture.
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end
end
這個自定義的驗證會去調用指定的 symbol(:picture_size)對應的方法。在 picture_size 方法中,如果圖片大於 5MB,就向 errors 集合增加一個自定義的錯誤訊息。
除了以上兩個驗證之外,我們還要在 client 端檢查上傳的圖片。首先,我們在 file_field 方法中使用 accept 參數限制圖片的格式:
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
有效的格式使用 MIME types 指定,這些類型對應到 app/uploaders/picture_uploader.rb 裡限制的類型。
接著,我們要使用一些 JavaScript(更確切地說是 JQuery),如果使用者嘗試上傳太大的圖片,就彈出一個提示視窗(節省了上傳的時間,也減少 server 的負擔):
$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
監視頁面中的 #micropost_picture id 元素,當這個元素的內容發生變化時,會執行這段程式碼;如果檔案太大,就會調用 alert 方法。
可以試著搜尋「javascript maximum file size」,會找到一些解法
把上面兩個檢查加進 micropost 表單中:
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost, html: { multipart: true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="picture">
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>
<% end %>
<script type="text/javascript">
$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
</script>
你可以看到,如果嘗試上傳很大的檔案,根據上述程式碼無法真正的清空 file input 欄位,使用者可以關掉 alert 視窗然後繼續嘗試上傳檔案(打死他)。如果這裡是在教 JQuery 的話,也許我們會嘗試去把欄位清掉。但其實前端的程式碼無法真的阻止使用者做蠢事的決心,使用者也許會透過 web inspector 來編輯 JavaScript,或是直接發送 POST 請求(例如,curl)。為了阻止使用者上傳太大的檔案,還是有必要在 server 端加上驗證。