Upload file có kích thước lớn trong rails.
Vấn đề Cho phép người dùng tải lên tập tin lớn lên server. Việc tải lên 1 file trong rails rất dễ dàng, nhưng chỉ khi tệp nhỏ. Hãy thử tải lên tệp có dung lượng trên 1 GB web của bạn sẽ treo trong một thời gian dài, điều này sẽ gây khó chịu cho người dùng, họ không hiểu chuyện gì đang xảy ra. Đó ...
Vấn đề
Cho phép người dùng tải lên tập tin lớn lên server. Việc tải lên 1 file trong rails rất dễ dàng, nhưng chỉ khi tệp nhỏ. Hãy thử tải lên tệp có dung lượng trên 1 GB web của bạn sẽ treo trong một thời gian dài, điều này sẽ gây khó chịu cho người dùng, họ không hiểu chuyện gì đang xảy ra. Đó là bởi vì thời gian để chuyển tệp từ client sang server qua http quá dài. Một cách để giải quyết vấn đề này là tải lên tệp ở kích thước nhỏ hơn và kết hợp các tệp tin tải lên nhỏ trở lại khi quá trình tải lên hoàn tất. Để làm được điều đó, chúng ta cần một cách để chia tệp thành nhiều phần nhỏ hơn và gửi lên máy chủ. Và jQuery có một plugin hỗ trợ việc này.
1. Cài đặt.
Ở đây mình sử dụng gem để cài đặt plugin.
gem "jquery-fileupload-rails"
application.js
# require tất cả //= require jquery-fileupload # require chỉ những cái mình sử dụng để upload video //= require jquery-fileupload/basic //= require jquery-fileupload/jquery.fileupload //= require jquery-fileupload/jquery.fileupload-process //= require jquery-fileupload/jquery.fileupload-video
application.css
*= require jquery.fileupload *= require jquery.fileupload-ui
2. Sử dụng.
a. tạo form.
đầu tiên thì phải tạo form để người dùng có thể chọn 1 file.
<%= form_for @video class: "form-horizontal form-bordered" do |f| %> <%= f.file_field :path, class:"upload-video", accept: "video/mp3, video/mp4, video/3gp" %> <div id="progress"> <div class="bar bar-upload"></div> </div> <%= f.button "upload", class: "btn btn-sm btn-primary" %> <% end %>
progess sẽ là phần hiển thị phần trăm file đã được tải lên.
b. Client.
Bây giờ chúng ta sẽ chia file ra nhiều phần nhỏ hơn và tiến hành upload.
$(document).ready(function () { var files = []; $('#new_video').on('submit', function(e) { e.preventDefault(); if (files.length < 1) return; $.each(files, function(index, file) { file.submit(); }); files = []; }); $('#new_video').fileupload({ dataType: 'json', method: 'post', url: '/videos/upload', maxChunkSize: 10000000, //KB mỗi lần tải lên. 10MB add: function(e, data) { $.each(data.files, function(index, file) { $('#name-file').text(file.name); $.ajax({ method: 'post', dataType: 'json', url: '/videos', data: {filename: file.name}, success: function(res) { data.formData = res; files = []; files.push(data); } }); }); }, // function này sẽ tính toán và hiển thị % upload progressall: function(e, data) { var done = parseInt(data.loaded * 100) / data.total $('#progress .bar').css({ awidth: done + '%'}); } }); });
Đầu tiên thì phía client sẽ thực hiện 1 post request lên controller để create record. Và sau đó sẽ post từng phần của file lên server.
c. Controller
Server. Tạo record trong db của chúng ta.
def create filename = params[:filename] uuid = SecureRandom.uuid ext = File.extname(filename) dir = Rails.root.join('public', 'uploads').to_s FileUtils.mkdir_p(dir) unless File.exist?(dir) @video = Video.new name: filename, path: File.join(dir, "#{uuid}#{ext}") if @video.save render json: {id: @video.id, uploaded_size: @video.uploaded_size} else render json: {error: @video.errors} end end
Tiếp theo thì chúng ta sẽ nhận được dữ liệu từ client gửi lên. Dữ liệu sẽ được gửi liên tục đến khi hết file. ở đây chúng ta sẽ thực hiện nối file. tương tự dữ liệu trả về sẽ là id và size đã upload được để chúng ta có thể tính toán và hiển thị % upload
def upload @video = Video.find_by id: params[:id] if @video && params[:video][:path] file = params[:video][:path] @video.uploaded_size += file.size if @video.save File.open(Rails.root.to_s + '/public/uploads' + @video.path, 'ab'){|f| f.write(file.read)} render json: {id: @video.id, uploaded_size: @video.uploaded_size} else render json: {error: @video.errors} end end end