carrierwave 是 rails 中可以上传文件的工具库,虽然 rails 在 5.2 版本之后已经提供了 active storage,更安全、功能强大,但 carrierwave 用起来比较简单,适合小的项目。
本文要讲的是使用 api 来上传音频文件到 rails 服务器的办法。
https://github.com/carrierwaveuploader/carrierwave
star: 8.6k
根据 ruby toolbox 统计,其在文件上传中排名第二,第一的 paperclip 已经停更了。
首先,在 Gemfile 中添加
gem 'carrierwave'
安装好
bundle install
生成一个音频上传类
rails generate uploader Soundfile
会保存在 app/uploaders/soundfile_uploader.rb 中,其内容如下:
class SoundfileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# 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
"storage/#{model.class.to_s.underscore}/#{mounted_as}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url(*args)
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [50, 50]
# end
# Add an allowlist of extensions which are allowed to be uploaded.
# For images you might use something like this:
# def extension_allowlist
# %w(jpg jpeg gif png)
# end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# if original_filename
# original_filename.force_encoding 'UTF-8'
# end
# end
end
指定了你要保存文档的位置,我是想把文件上传后保存到服务器中,所以 storage :file 并不需要改动。如果想保存到云存储中,请参考官方文档。
里面你可以设置成自己想要保存的路径,其默认是保存在 public 目录下,类似于这样:public/storage/practice/文件名,这样上传之后是可以通过 url 来访问的,可以根据你自己的需求调整。也可以使用绝对路径,比如/home/user/storage,请确认你有对应的权限。
我想给练习添加一个音频
rails g migration add_soundfile_to_practices soundfile:string
rails db:migrate
db/migrate/20210818034921_add_soundfile_to_practices.rb内容如下:
class AddSoundfileToPractices < ActiveRecord::Migration[6.0]
def change
add_column :practices, :soundfile, :string
end
end
在 practice.rb 中建立关联
记得要使用 mount_uploader, 如果是多个文件,需要使用 mount_uploaders, 后面多个 S!
class Practice < ApplicationRecord
belongs_to :user
belongs_to :pth_sentence
mount_uploader :soundfile, SoundfileUploader
end
app/controllers/practices_controller.rb中主要代码
def new
@practice = Practice.new
end
def create
@practice = Practice.new(practice_params)
if @practice.save
render json: { status: 'success', msg: '创建成功' }
else
render :new
end
end
def practice_params
prms =
params.require(:practice).permit(
:user_id,
:pth_sentence_id,
:soundfile
)
# prms[:soundfiles].first.headers.force_encoding 'utf-8'
prms
end
routes.rb 中要有路由
resources :practices
如果你使用 web 上传,那么可以在 views 中这样来写(app/views/practices/_form.html.erb):
<%= form_for(@practice) do |f| %>
<%= f.text_field 'user_id' %>
<%= f.text_field 'pth_sentence_id' %>
<%= f.file_field :soundfile %>
<%= f.submit %>
<% end %>
参数要组装成 rails 需要的格式,类名 [属性],客户端我用的是 uniapp, 所以用 uniapp 来举例。
uni.uploadFile({
url: `http://192.168.1.8:3000/practices`,
name: 'practice[soundfile]',
filePath:this.voicePath,
formData: {
'practice[user_id]': 1,
'practice[pth_sentence_id]': 30,
},
success: (uploadFileRes) => {
console.log(uploadFileRes);
},
fail: (msg) => {
console.log(msg.error)
}
});
uniapp 提供了 uploadFile 的 API:
filePath 是要上传的文件名称
name 是参数名称,注意其写法为 practice[soundfile],要与 model 中对应
其它参数写在 formData 中。
测试成功,文件已经上传到指定的目录中
观察后台,可以看到 soundfile 已经保存好了。
[1] pry(main)> u= User.find 6
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 LIMIT 1
=> #<User id: 6, email: "[email protected]", created_at: "2021-08-18 03:25:59", updated_at: "2021-08-18 03:25:59", phone: "13317555983", name: "king1", title: nil>
[2] pry(main)> u.practices.last
Practice Load (0.2ms) SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> #<Practice:0x00007f9c9c83dca0
id: 8,
user_id: 6,
pth_sentence_id: 44,
local_sound: nil,
comment: nil,
created_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
updated_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
soundfile: "07-最后他选择了后者_.mp3">
[4] pry(main)> u.practices.last.soundfile.url
Practice Load (0.4ms) SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> "/storage/practice/soundfile/07-%E6%9C%80%E5%90%8E%E4%BB%96%E9%80%89%E6%8B%A9%E4%BA%86%E5%90%8E%E8%80%85_.mp3"
[5] pry(main)> u.practices.last.soundfile.current_path
Practice Load (0.5ms) SELECT `practices`.* FROM `practices` WHERE `practices`.`user_id` = 6 ORDER BY `practices`.`id` DESC LIMIT 1
=> "/home/study/funny_words/public/storage/practice/soundfile/07-最后他选择了后者_.mp3"
使用 mount_uploaders, sssss, 复数!我恨复数,我恨复数,我恨复数。
编码问题导致的,经分析,是 headers 中的 filename 编码有问题,见上图 (吐槽:外国人开发的工具总是容易产生国际化问题),经搜索官网 issues,可能的解决办法如下。
1、application.rb 中添加 config.encoding = "utf-8"
2、在 practices_controller.rb 的 practice_params 中添加 prms[:soundfile].first.headers.force_encoding 'utf-8'
def practice_params
prms =
params.require(:practice).permit(
:user_id,
:pth_sentence_id,
:soundfile
)
prms[:soundfile].headers.force_encoding 'utf-8'
prms
end
这样就会把 headers 强制转成 utf-8 编码。
使用 API 上传的时候,可能会遇到 CSRF 问题,这是为了防止一些网络攻击,可以通过在 application.rb 中添加
config.action_controller.default_protect_from_forgery = false
来关闭此项检查,这样开发时就方便喽。注意!在生产环境中要使用其它安全令牌比如 jwt 来进行合法用户校验。
ruby 2.6.5, rails 6.0.4, mysql 5.7(utf8mb4)